Contact Us

Primitive Components

DOC-00010 reference implementor, developer

Primitive components are the lowest-level UI building blocks. They live in src/core/components/primitives/ and are CORE-OWNED. Higher-level sections and page layouts compose these primitives.

Who this is for

  • Implementors building pages or section components
  • Developers extending the component library
  • AI agents composing UI from primitives

Icon

File: src/core/components/primitives/Icon.astro

Renders an inline SVG icon by name. Icons are resolved at build time using a layered lookup: src/site/assets/icons/ first, then src/core/assets/icons/, across utility/ and social/ subdirectories. Site icons shadow core icons of the same name.

Props

PropTypeDefaultNotes
namestringrequiredIcon filename without extension (e.g., "envelope")
sizestring"1.25em"CSS size applied to width and height
labelstringAccessible label. When set, icon is announced to screen readers. When omitted, icon is decorative (aria-hidden).
classstringAdditional CSS classes

Behavior

  • Icons are resolved in order: site/utility/site/social/core/utility/core/social/. The first match wins, so site icons shadow core icons of the same name.
  • The component throws a build error if the icon name cannot be resolved — no silent failures.
  • SVG color inherits from the parent’s color property via currentColor. Set color on the parent element.

Heading

File: src/core/components/primitives/Heading.astro

Semantic heading with visual size decoupling. The level prop controls the HTML element (h1h6), while size overrides the visual styling independently.

Props

PropTypeDefaultNotes
level1|2|3|4|5|6requiredSemantic heading level (HTML element)
size"xs"|"sm"|"md"|"lg"|"xl"|"2xl"|"3xl"|"4xl"|"5xl"mapped from levelVisual size. Decoupled from semantic level using primitive typography tokens.
tocbooleantrueWhen false, renders data-toc="exclude" on the heading element.
classstringAdditional CSS classes

Behavior

  • Semantic–visual split: Use level for document outline correctness and size for visual hierarchy. Example: a sidebar h2 that should look like an h4<Heading level={2} size="lg">.
  • Size classes apply font-size and line-height from primitive tokens.
  • TOC exclusion: When toc={false}, the heading renders data-toc="exclude" as an HTML attribute. The docs route client-side fallback script filters out headings with this attribute. Use this for component-rendered headings that should not appear in the table of contents (e.g., section headings in showcase pages, widget titles).
<!-- This heading appears in the TOC (default) -->
<Heading level={2} id="visible">Visible heading</Heading>

<!-- This heading is excluded from the TOC -->
<Heading level={2} id="hidden" toc={false}>Hidden from TOC</Heading>

Note: TOC exclusion applies only to component-rendered headings in .astro templates. Markdown-authored headings (## Foo) cannot carry the data-toc="exclude" attribute. See the Table of Contents documentation for details.

PageHeading

File: src/core/components/primitives/PageHeading.astro

Lightweight page heading band for route subheaders. Use instead of Hero when no actions, images, or complex layout are needed. Composes Heading with an optional subheading paragraph.

Props

PropTypeDefaultNotes
headingstringrequiredPage title text
subheadingstringOptional subtitle rendered as a paragraph below the heading
headingLevel1|2|3|4|5|61Semantic heading level (HTML element)
headingSize"xs"|"sm"|"md"|"lg"|"xl"|"2xl"|"3xl"|"4xl"|"5xl"mapped from levelVisual size override, passed through to Heading
classstringAdditional CSS classes on the wrapper

Behavior

  • Renders a Heading and an optional <p> inside a flex column with gap-sm.
  • Subheading uses --pt-text-lg font size, relaxed line height, and 90% opacity — matching the same visual treatment as Hero’s simple render path.
  • Subheading has max-inline-size: 60ch to maintain comfortable reading width.
  • No <style> block, no JavaScript, no additional component imports beyond Heading.

When to use

Use PageHeading for route subheader bands where the page needs only a title and optional description. Use Hero when the heading band requires call-to-action buttons, images, or complex grid layouts.

<LayoutSection
  as="div"
  slot="subheader"
  contentWidth="wide"
  verticalPadding="lg"
>
  <PageHeading
    heading="Documentation"
    subheading="Browse docs and showcase pages by topic, type, and audience."
  />
</LayoutSection>

Button

File: src/core/components/primitives/Button.astro

Polymorphic button that renders as <a> when href is provided, or <button> otherwise.

Props

PropTypeDefaultNotes
variant"primary"|"outline"|"secondary"|"secondary-outline"|"tertiary"|"tertiary-outline"|"neutral"|"inverted"|"on-primary"|"on-secondary"|"on-tertiary""primary"Visual style variant
size"sm"|"md"|"lg""md"Size tier
hrefstringWhen set, renders <a> instead of <button>
disabledbooleanfalseDisables interaction. On links, removes href and adds aria-disabled.
fullWidthbooleanfalseStretches to 100% container width
tokenOverridesButtonTokenOverridesOne-off semantic-token overrides for rare treatments. Values must be var(--st-color-...) refs
classstringAdditional CSS classes

Variant roles

  • primary — default high-emphasis action on standard surfaces
  • outline — hollow/ghost primary action on standard surfaces (primary action token family)
  • secondary — secondary-brand action on standard surfaces
  • secondary-outline — hollow/ghost secondary action on standard surfaces
  • tertiary — tertiary-brand action on standard surfaces
  • tertiary-outline — hollow/ghost tertiary action on standard surfaces
  • neutral — non-brand low-emphasis action on standard surfaces
  • inverted — theme-opposite high-contrast action on contrast and inverted surfaces
  • on-primary — contextual action for primary brand surfaces
  • on-secondary — contextual action for secondary brand surfaces
  • on-tertiary — contextual action for tertiary brand surfaces

Behavior

  • Polymorphic rendering: With href, emits <a> (no type attribute). Without href, emits <button type="button"> (overridable via type prop).
  • Disabled links: When disabled is true on a link variant, href is removed and aria-disabled="true" is set. The cursor changes to not-allowed and opacity is reduced.
  • Border radius: Controlled via Tailwind radius utility classes on class (e.g., rounded-none, rounded-sm, rounded-lg, rounded-full). The component stylesheet provides a default radius; consumers override with utility classes as needed.
  • Token override escape hatch: tokenOverrides accepts an object with optional bg, fg, border, bgHover, and fgHover fields. Each field must be a semantic token reference (var(--st-color-...)). Use this for rare one-off treatments not covered by the named variants — for example, a destructive action button using var(--st-color-status-error).
  • Style passthrough: The style prop is preserved for non-Button-owned layout concerns (e.g., align-self). Button-owned CSS variables (--_btn-*, --btn-*) are stripped from incoming style to keep tokenOverrides as the only authorized color-override channel.
  • Color tokens use the --st-color-action-* family. Focus ring uses --st-color-focus-ring.
  • Each Button variant maps its focus ring to the matching --st-color-action-*-focus token so keyboard focus follows the active action palette.

File: src/core/components/primitives/Link.astro

Internal link wrapper with token-driven colors and accessible focus states.

Props

PropTypeDefaultNotes
hrefstringrequiredDestination URL
classstringAdditional CSS classes

Behavior

  • Applies --st-color-link, --st-color-link-hover, and --st-color-link-visited tokens.
  • Focus ring styled with --st-color-focus-ring.
  • All additional props are spread onto the <a> element.

File: src/core/components/primitives/ExternalLink.astro

External link with security attributes, screen reader announcement, and optional icon.

Props

PropTypeDefaultNotes
hrefstringrequiredExternal URL
showIconbooleantrueShow external-link icon after link text
classstringAdditional CSS classes

Behavior

  • Always applies rel="noopener noreferrer" (REQ-00166, REQ-00167). Does not force target; pass target="_blank" explicitly when needed.
  • Appends <span class="sr-only">(external link)</span> for all external links. When target="_blank" is set, the text becomes (external link, opens in new tab).
  • When showIcon is true, appends an inline SVG external-link icon (0.65em, aria-hidden).

Badge

File: src/core/components/primitives/Badge.astro

Inline metadata badge with color variants aligned to the semantic token system.

Props

PropTypeDefaultNotes
variant"default"|"success"|"warning"|"error"|"info"|"brand-primary"|"brand-secondary"|"brand-tertiary""default"Color variant
size"sm"|"md""md"Size tier
outlinebooleanfalseTransparent background with border and text in the variant color. Use with high-contrast variants (error, info, brand-*).
classstringAdditional classes

Behavior

  • Status variants use --st-color-status-* backgrounds with --st-color-on-status-* text.
  • Brand variants use --st-color-brand-* and --st-color-on-brand-* tokens.
  • Default variant uses --st-color-surface-strong background.
  • All variants pass WCAG AA contrast in both light and dark mode.

FormatDate

File: src/core/components/primitives/FormatDate.astro

Date formatting component that renders a <time> element with machine-readable datetime attribute.

Props

PropTypeDefaultNotes
dateDate | stringrequiredDate to format. Strings are parsed via new Date().
format"short"|"long"|"iso""short"Preset format
optionsIntl.DateTimeFormatOptionsCustom Intl options. Overrides format preset.
classstringAdditional CSS classes

Format presets

PresetExample outputDescription
shortJan 15, 2026Month abbreviation, day, year
longJanuary 15, 2026Full month name, day, year
iso2026-01-15ISO 8601 date string

Locale is sourced from siteConfig.locale. The datetime attribute always contains the ISO string regardless of display format.

LayoutSection

File: src/core/components/primitives/LayoutSection.astro

Full-width section wrapper with background variants, vertical-padding tiers, and a built-in inner content frame.

Props

PropTypeDefaultNotes
as"section"|"div""section"HTML element to render. Use "div" for non-landmark wrappers
labelstringAccessible name for the section. Applied as aria-label only when as="section". Ignored on <div>. Prefer over direct aria-label; if both are present, label wins.
background"default"|"soft"|"strong"|"contrast"|"inverted"|"primary"|"secondary"|"tertiary""default"Surface color variant via shared .surface--bg-* classes
contentWidth"content"|"content-wide"|"wide"|"full"|"inherit""inherit"Inner frame max-width. inherit defers to page-level section width context.
gutter"default"|"none""default"Inner frame horizontal padding. default uses --pt-layout-container-gutter
verticalPadding"inherit"|"none"|"sm"|"md"|"lg"|"xl"|"2xl"|"3xl""inherit"Vertical padding tier. inherit applies no explicit section padding class.
classstringAdditional classes on the outer element

Behavior

  • The outer element (default <section>, configurable via as) is full-width with the selected background color.
  • LayoutSection always renders an inner Container frame for consistent content alignment.
  • Default frame width is inherited from PageGridLayout contentWidth via CSS variable; if unavailable, it falls back to wide.
  • LayoutSection is recommended for semantic page bands, but not required for basic layout framing: PageGridLayout also applies a default frame to direct non-LayoutSection children in the main slot.
  • Background variants map to --st-color-surface-* tokens. The inverted variant also sets text color to --st-color-text-on-inverted.
  • Vertical-padding tiers use primitive space tokens (--pt-space-*).
  • gutter="none" removes inner horizontal padding while preserving max-width behavior.

Choosing as and label

Each LayoutSection usage falls into one of three categories:

CategoryWhen to useExample
as="div"Parent slot already provides a landmark (subheader inside <header>, main-header/main-footer inside a labeled <section>); or content is not a thematic grouping (pagination, individual demos, error messages).<LayoutSection as="div" slot="subheader">
Keep as="section" (default, no label)Content is a thematic grouping with a visible heading — either via a sibling <Heading> component or markdown headings. The heading provides the accessible name.<LayoutSection> with a sibling <Heading> before a section component
as="section" + labelGenuine thematic grouping without a visible heading (listing grids, search interfaces, filter UIs).<LayoutSection label="Article listing">

The label prop is conditionally applied: it sets aria-label only when as="section" (the default). When as="div", the label is ignored because a generic <div> has no implicit ARIA role to label. If both label and a direct aria-label are passed, label takes precedence and a dev-time warning is emitted.

File: src/core/components/primitives/Dropdown.astro

Accessible dropdown using the disclosure pattern, powered by vanilla TypeScript. Tab navigates items; arrow keys provide supplemental navigation.

Props

PropTypeDefaultNotes
labelstring"Menu"Accessible label for trigger
position"bottom-start"|"bottom-end""bottom-start"Panel alignment
classstringAdditional classes

Slots

SlotPurpose
triggerCustom trigger element. If omitted, a default button with label renders.
defaultMenu items. Use <a> or <button> elements.

Behavior

  • Uses the disclosure pattern (not ARIA menu). Tab/Shift+Tab navigates between items; arrow keys and Home/End provide supplemental navigation.
  • Closes when focus leaves the panel (focusin.window), on click outside, or on Escape (returns focus to trigger).
  • Uses vanilla JS for focus-leave detection.
  • Panel appears/hides with CSS transitions. Hidden by default to prevent flash of unstyled content.
  • No-JS: Without JavaScript, the dropdown does not render (hidden by default).
  • The trigger and panel consume the nav dropdown semantic surface (--st-color-nav-dropdown-bg / --st-color-nav-dropdown-border) rather than generic surface tokens.

ListBox

Relocated: ListBox has moved from src/core/components/primitives/ to src/core/components/forms/ as part of the forms system (D-C8-11). See the Forms & Submission documentation for usage.

Vanilla JS implementation

Dropdown and ListBox (now in src/core/components/forms/) use vanilla TypeScript for state management, keyboard navigation, focus handling, and ARIA attributes. No framework dependencies are required.

Manual accessibility test entries verified during the accessibility audit. Covers keyboard operability, screen-reader announcements, and ARIA semantics.

Button

Interaction Expected Behavior WCAG Criterion Test Method
Tab to the Button componentButton receives visible focus ring using the canonical focus style2.4.7 Focus Visible Keyboard
Press Enter or Space on the Button componentButton activates its action (navigation for link buttons, click handler for button elements)2.1.1 Keyboard Keyboard
Inspect the Button component in DevToolsRenders as <button type="button"> by default or <a> when href is provided. Disabled link buttons have href removed and aria-disabled="true". No orphaned ARIA attributes on enabled buttons.4.1.2 Name, Role, Value Visual Inspection
Focus the Button component with NVDA runningNVDA announces the button with a descriptive label matching its visible text content4.1.2 Name, Role, Value Screen Reader
Compare the Button visible label against its NVDA-announced accessible nameNVDA announces the button with a descriptive label matching its visible text content for both button and link variants2.5.3 Label in Name Screen Reader
REQ-00043 implemented The theme shall include foundational UI primitives.
REQ-00045 normative Components shall encapsulate styling and behavior.
REQ-00046 normative Framework-free components are preferred.
REQ-00047 normative Components shall expose stable public APIs.
REQ-00052 implemented Button components shall support defined visual variations.
REQ-00166 implemented Any external link that opens in a new tab (target='_blank') shall include rel='noopener noreferrer'. External links in prose content shall not open in a new tab by default; only links where the author explicitly intends a new-tab experience (e.g. social icons, certain CTAs) shall use target='_blank'.
REQ-00167 implemented External links in prose content shall render a visually distinct icon (e.g. external-link glyph) adjacent to the link text. Links that open in a new tab shall additionally include a visually hidden announcement (e.g. '(opens in new tab)') so screen reader users are informed of the context change.
REQ-00185 implemented Button components shall support Primary, Outline, Secondary, Secondary Outline, Tertiary, Tertiary Outline, Neutral, Inverted, On Primary, On Secondary, and On Tertiary visual variations. Outline variations represent hollow/ghost treatments reusing their matching action token family. Contextual on-* variants are designed for use on matching brand-colored surfaces. A documented semantic-token override escape hatch allows rare one-off treatments using semantic token references. Per-instance border-radius is controlled via Tailwind utility classes on class, not a dedicated prop.

Search

Search across pages and articles. Use arrow keys to navigate results.

Search across pages and articles.

Loading search...

Search is unavailable. Please try again later.

    No results for ""

    Try different keywords or fewer words.