defuss-shadcn / Website / Article / article-body
Article BodyBLK
Renders an article's formatted text and embedded media - headings, lists, quotes, code, tables, figures, video and iframes, from plain HTML. Part of the Article blocks.
On this page (6)
§Default
A lead paragraph, Heading Anchors (§), a footnote ref, del / ins, a pull quote, Kbd keys, a Code Mockup with a diff, an Alert callout, a Table with Badges, an Image with its caption, a definition list and the footnotes - the components keep their own look, the prose rules only style class-less elements. Click the footnote 1: it jumps to the note and back.
§Plain HTML (CMS output)
What a CMS or Markdown renderer emits - headings, a list, a quote, inline and block code, a figure, a callout (.mk-article-body-note), a table, a video and a rule. No classes on the elements.
§Serif with a drop cap
data-variant='serif' sets the running text in the serif stack (headings stay sans); data-dropcap opens the first paragraph.
§Sizes
data-size='sm' for dense docs, 'lg' (19px, a wider measure) for long reads.
§Beside a table of contents, with reading progress
The text in .mk-article-body-layout beside a sticky TOC component (from 56rem - narrow the demo and it hides). Scroll the box: .mk-article-body-progress fills as you read - a scroll-driven animation, no script.
§CSS view file
/* -- Article Body (website) ----------------------------------------- Renders an article's formatted text. Plain elements - what a CMS or a Markdown renderer emits - are styled without classes; library components dropped into the text (.mockup-code, .table, .alert, .image, .kbd, .heading-anchor, .toc…) keep their own look, because the prose rules only reach class-less elements. Adds a lead, pull quotes, footnotes, a table of contents layout and a reading-progress bar. */@layer components { .mk-article-body { --_measure: 42rem; box-sizing: border-box; width: 100%; max-width: var(--_measure); margin-inline: auto; padding-inline: 1.25rem; color: var(--foreground); font-size: 1.0625rem; line-height: 1.75; hanging-punctuation: first last; overflow-wrap: break-word; & > * { margin-block: 0; } & > * + * { margin-block-start: 1.25em; } & :is(h2, h3, h4) { line-height: 1.25; letter-spacing: -0.02em; text-wrap: balance; scroll-margin-block-start: 5rem; } & > h2 { margin-block-start: 2em; font-size: 1.625em; font-weight: 700; } & > h3 { margin-block-start: 1.75em; font-size: 1.25em; font-weight: 600; } & > h4 { margin-block-start: 1.5em; font-size: 1em; font-weight: 600; } & > :is(h2, h3, h4) + * { margin-block-start: 0.75em; } & p { text-wrap: pretty; } /* prose links - links with a class are components (heading anchors, buttons…) */ & a:not([class]) { color: var(--primary); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em; &:hover { text-decoration-thickness: 2px; } &:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 2px; } } & :is(strong, b) { font-weight: 650; } & > :is(ul, ol) { padding-inline-start: 1.5em; } & > :is(ul, ol) li + li { margin-block-start: 0.4em; } & > :is(ul, ol) li::marker { color: var(--muted-foreground); } & > dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.5em 1.25em; & dt { font-weight: 600; } & dd { margin: 0; color: var(--muted-foreground); } } & > blockquote:not([class]) { margin-inline: 0; padding-inline-start: 1.25em; border-inline-start: 3px solid var(--primary); font-size: 1.125em; font-style: italic; & p { margin: 0; } & cite { display: block; margin-block-start: 0.5em; color: var(--muted-foreground); font-size: 0.8em; font-style: normal; &::before { content: "- "; } } } & :not(pre) > code:not([class]) { padding: 0.125em 0.375em; border-radius: var(--radius-sm); background: var(--muted); font-family: var(--font-mono); font-size: 0.875em; } /* a bare <pre>; a .mockup-code brings its own window */ & > pre { padding: 1em 1.25em; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--muted); font-family: var(--font-mono); font-size: 0.875em; line-height: 1.6; tab-size: 2; } & > hr { height: 1px; margin-block: 2.5em; border: 0; background: var(--border); } & figure:not([class]) { margin-inline: 0; & :is(img, video) { display: block; width: 100%; height: auto; border-radius: var(--radius-lg); } & figcaption { margin-block-start: 0.625em; color: var(--muted-foreground); font-size: 0.8125em; text-align: center; } } & > :is(img, video) { display: block; width: 100%; height: auto; border-radius: var(--radius-lg); } /* embeds (YouTube, maps…): 16:9 unless the iframe says otherwise */ & iframe { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; border: 0; border-radius: var(--radius-lg); background: var(--muted); } /* a bare table; a .table (in its .table-container) is the component */ & > table { display: block; width: 100%; overflow-x: auto; border-collapse: collapse; font-size: 0.875em; line-height: 1.5; & :is(th, td) { padding: 0.5em 0.75em; border-block-end: 1px solid var(--border); text-align: start; } & th { color: var(--muted-foreground); font-weight: 600; } } & kbd:not([class]) { padding: 0.0625em 0.375em; border: 1px solid var(--border); border-block-end-width: 2px; border-radius: var(--radius-sm); font-family: var(--font-mono); font-size: 0.8em; } & mark { padding-inline: 0.125em; border-radius: 2px; background: color-mix(in oklch, var(--primary) 22%, transparent); color: inherit; } & abbr[title] { text-decoration: underline dotted; text-underline-offset: 0.2em; cursor: help; } & :is(del, s) { color: var(--muted-foreground); } & ins { text-decoration: none; background: color-mix(in oklch, oklch(0.72 0.17 150) 22%, transparent); } /* the library components inside the text */ & > :is(.mockup-code, .table-container, .alert, .image, .mk-article-body-pullquote) { margin-block-start: 1.75em; } & > :is(.mockup-code, .table-container, .alert, .image) + * { margin-block-start: 1.75em; } & .heading-anchor { margin-inline-end: 0.25em; } } /* the opening paragraph, larger */ .mk-article-body-lead { color: var(--muted-foreground); font-size: 1.25em; line-height: 1.6; } /* a callout box inside the text */ .mk-article-body-note { padding: 1em 1.25em; border: 1px solid var(--border); border-inline-start: 3px solid var(--primary); border-radius: var(--radius-md); background: color-mix(in oklch, var(--muted) 60%, transparent); font-size: 0.9375em; & > :first-child { margin-block-start: 0; } & > :last-child { margin-block-end: 0; } } /* a pull quote - set large between rules, the line repeated from the text */ .mk-article-body-pullquote { margin-inline: 0; padding-block: 0.75em; border-block: 1px solid var(--border); font-family: var(--font-serif); font-size: 1.5em; font-weight: 600; line-height: 1.35; text-align: center; text-wrap: balance; &::before { content: "“"; display: block; color: var(--primary); font-size: 2em; line-height: 0.5; } } /* footnotes: numbered refs jump to the notes, the notes jump back */ .mk-article-body-ref { font-size: 0.75em; line-height: 0; vertical-align: super; & a { padding-inline: 0.125em; color: var(--primary); font-weight: 600; text-decoration: none; } & a:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; border-radius: 2px; } } .mk-article-body-footnotes { padding-block-start: 1em; border-block-start: 1px solid var(--border); color: var(--muted-foreground); font-size: 0.875em; & ol { margin: 0; padding-inline-start: 1.25em; } & li { padding: 0.25em 0.5em; margin-inline-start: -0.5em; border-radius: var(--radius-sm); scroll-margin-block-start: 5rem; } & li:target { background: color-mix(in oklch, var(--primary) 12%, transparent); color: var(--foreground); } & a[href^="#"] { color: var(--primary); text-decoration: none; } } /* a wider figure than the measure (it stays inside the viewport) */ .mk-article-body > .mk-article-body-wide { width: min(100vw - 2.5rem, var(--_measure) + 16rem); margin-inline: calc((100% - min(100vw - 2.5rem, var(--_measure) + 16rem)) / 2); } /* the text beside a sticky table of contents (the .toc component) */ .mk-article-body-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; box-sizing: border-box; width: 100%; max-width: 64rem; margin-inline: auto; & > .toc { display: none; } & > .mk-article-body { margin-inline: 0; } } @media (min-width: 56rem) { .mk-article-body-layout { grid-template-columns: 12rem minmax(0, 1fr); & > .toc { display: flex; position: sticky; inset-block-start: 1rem; align-self: start; } } } /* a reading-progress bar - driven by the scroll of the nearest scroller */ .mk-article-body-progress { position: sticky; inset-block-start: 0; z-index: 2; height: 3px; margin-block-end: 1rem; background: var(--primary); transform-origin: left; scale: 0 1; animation: mk-article-body-read linear both; } /* the timeline in its own rule: a minifier must not fold it into the `animation` shorthand (where it is reset-only - the declaration would be dropped); unsupported, the bar hides */ @supports (animation-timeline: scroll()) { .mk-article-body-progress { animation-timeline: scroll(nearest block); } } @supports not (animation-timeline: scroll()) { .mk-article-body-progress { display: none; } } /* serif: an editorial voice for the running text */ .mk-article-body[data-variant="serif"] { font-family: var(--font-serif); font-size: 1.1875rem; & :is(h2, h3, h4) { font-family: var(--font-sans); } } /* sizes */ .mk-article-body[data-size="sm"] { font-size: 0.9375rem; line-height: 1.7; } .mk-article-body[data-size="lg"] { font-size: 1.1875rem; --_measure: 46rem; } /* a drop cap opens the first paragraph */ .mk-article-body[data-dropcap] > p:first-of-type::first-letter { float: inline-start; margin: 0.06em 0.12em 0 0; font-family: var(--font-serif); font-size: 3.6em; font-weight: 700; line-height: 0.8; color: var(--primary); } @keyframes mk-article-body-read { to { scale: 1 1; } } /* -- Accessibility ------------------------------------------- */ @media (prefers-reduced-motion: reduce) { .mk-article-body-progress { animation: none; display: none; } } @media (prefers-contrast: more) { .mk-article-body :is(blockquote cite, figcaption), .mk-article-body-lead, .mk-article-body-footnotes { color: var(--foreground); } .mk-article-body li::marker { color: var(--foreground); } .mk-article-body a:not([class]) { text-decoration-thickness: 2px; } } @media (forced-colors: active) { .mk-article-body a:not([class]) { color: LinkText; } .mk-article-body :not(pre) > code:not([class]), .mk-article-body > pre { border: 1px solid CanvasText; } .mk-article-body-progress { background: Highlight; } }}Comments, ideas or improvements? Edit this page's source on GitHub