Article Template

Full-page surface for long-form articles. Composes the header (title + AuthorByline), the body (Prose-wrapped MDX/HTML), the related-posts grid, prev/next navigation, and share buttons — each in its own `<SectionBoundary>` so the page streams section-by-…

blog & long-formserver320pxblockv1.1.0

Preview

Article Template — live rendertemplates-articletemplate--default

Rendered from the story the storybook (a11y) CI gate runs axe against — the preview can't show something that hasn't been verified.

Install

Two equivalent paths — the URL works in any shadcn-CLI setup; the alias works once you've registered @interlace in your components.json.

Via shadcn URL
npx shadcn@latest add https://ds.interlace.tools/r/article-template.json
With the @interlace alias
npx shadcn@latest add @interlace/article-template

History

This component is at v1.1.0, first shipped in @interlace/ui 1.0.0. The version is stamped as a banner into the file the install writes, so the copy in your tree says which one you have — compare it with the number above before deciding whether to re-run the install.

Import

2 named exports — pull the parts you need.

Public API
import { ArticleTemplate, MIN_VIEWPORT } from '@/components/ui/templates/article-template';

Anatomy

Extracted from the primitive's JSDoc header. The source is the only documentation that can't drift.

<article data-slot="article-template" data-min-viewport="320">
    <Container size="prose">
      <SectionBoundary name="article-header">
        <Typography variant="h1">{title}</Typography>
        <AuthorByline {...byline} />
      </SectionBoundary>
      <SectionBoundary name="article-body">
        <Prose>{body}</Prose>
      </SectionBoundary>
      <SectionBoundary name="article-share">
        <ShareButtons {...share} />
      </SectionBoundary>
      <SectionBoundary name="article-prev-next">
        <PrevNextPost prev={...} next={...} />
      </SectionBoundary>
      <SectionBoundary name="article-related">
        <RelatedPosts posts={...} />
      </SectionBoundary>
    </Container>
  </article>

API reference

Parsed from the type declarations in the source — the same file the install writes into your tree, so this table can't drift from the component you get.

ArticleTemplateProps

Also accepts every <article> attribute.

PropTypeDescription
title*React.ReactNodeArticle title — rendered as the h1. Required (it's the landmark).
headerReact.ReactNodeAuthor + publish date + reading-time row, rendered under the title. Consumer can either pass the full ReactNode (max flexibility — e.g. to add their own meta chip) or use `byline` for the standard AuthorByline pattern.
bylineReact.ComponentProps<typeof AuthorByline>Shortcut: AuthorByline props. Ignored when `header` is provided.
body*React.ReactNodeThe article body. Wrapped in <Prose> for the canonical type contract. Pass either rendered MDX/HTML (`<div dangerouslySetInnerHTML={...} />`), a stream of MDX components, or any ReactNode.
prevNextReact.ComponentProps<typeof PrevNextPost>Prev/next links (footer of the article). Both are optional.
relatedReact.ComponentProps<typeof RelatedPosts>Related-articles grid (after prev/next).
shareReact.ComponentProps<typeof ShareButtons> | nullShare-buttons row. When provided, renders between body and prev/next. Set to `null` to suppress entirely (some publications hide social surfaces on niche posts).

Accessibility

Every story for this component is rendered headlessly and checked with axe-core (wcag2aa, wcag22aa, best-practice, ACT) on every PR. That gate has no continue-on-error, so what ships has zero known violations.

What follows is what static analysis can see. Axe cannot press a key and never sees an overlay open, so the operable-without-a-mouse claim lives in Behavior instead, where the keyboard path is replayed step by step.

Focus + keyboard behaviour
Native element semantics — no interaction layer to get wrong.
Focus ring (WCAG 2.2 SC 2.4.13)
Not focusable — no focus indicator required.
Reduced motion
No animation to gate.
ARIA in the source
  • role="status"
  • aria-busy
  • aria-label

Examples

4 more states from the same story file.

R-rule compliance

Every primitive in @interlace/ui models to the portable 26-rule floor enforced by the componentApi ESLint preset. The cells below pin exactly where each rule applies in this file.

RuleConceptWhere
R4Extends native el`React.ComponentProps<'article'>` + template props
R6data-slot on root`data-slot="article-template"`
R7className merged + ...rest`cn(className)` + `{...props}` on <article>
R10Composition seam (slots)`header` / `body` / `related` / `prevNext` / `shareUrl` props
R14Declares min viewport`data-min-viewport={String(MIN_VIEWPORT)}`
R18Tailwind onlyZero inline `style`; layout via Container + cn()
R19Tokens only(delegated to composed primitives — they own the tokens)
R20AA contrast(delegated)
R25Server componentPure composition — no hooks
R26A11y`<article>` landmark + per-section `<SectionBoundary>` regions

Minimum viewport

This primitive declares MIN_VIEWPORT = 320 CSS px (DESIGN_PRINCIPLES #14). When mounted in a container narrower than this, the preflight contract draws a dev-mode outline so the regression is visible during local development.

Opt-in to the dev-mode warning
<body data-interlace-dev>...</body>

Add the data-interlace-dev attribute to the body in development builds only — preflight then outlines any primitive whose container is below its declared data-min-viewport.

Dependencies

Base UI primitive
Native / no Base UI dependency
Lucide icons
none
NPM dependencies
none

Source

The full implementation — components/ui/templates/article-template.tsx once installed.

232 lines · TypeScript
import * as React from 'react';

// @interlace/article-template v1.1.0 — Interlace design system.
// Docs, props and live preview: https://ds.interlace.tools/c/article-template
// What changed since: https://ds.interlace.tools/c/article-template#history
// Generated banner — keep it, the upgrade diff reads this version.

/**
 * @interlace/ui — ArticleTemplate
 *
 * Full-page surface for long-form articles. Composes the header
 * (title + AuthorByline), the body (Prose-wrapped MDX/HTML),
 * the related-posts grid, prev/next navigation, and share buttons —
 * each in its own `<SectionBoundary>` so the page streams section-by-
 * section instead of blocking on the slowest data source.
 *
 * This is the canonical example of the 5-layer architecture in action:
 *
 *   • PRIMITIVES — Container, Prose, Typography (the chrome)
 *   • PATTERNS   — AuthorByline, RelatedPosts, PrevNextPost, ShareButtons

…212 more lines…

Registry JSON

The raw registry record — what the shadcn CLI fetches.