@phcdevworks/spectre-ui-astro is the Astro integration for the Spectre system.
It brings Spectre's design contracts and interface components into Astro
applications through a native, server-rendering-friendly API.
Maintained by PHCDevworks. It depends on
@phcdevworks/spectre-ui for its CSS and recipe contracts, so Astro
applications consume Spectre's design system through typed components instead of
hand-rolling markup or styling against the recipes directly.
| Field | Value |
|---|---|
| Project team | project-design |
| Repository role | Spectre L3b Astro adapter |
| Package/artifact | @phcdevworks/spectre-ui-astro |
| Current version/status | 4.14.0 |
- Read AGENTS.md, then the agent-specific guide for the task.
- Check TODO.md and ROADMAP.md for current scope.
- Make the smallest repo-local change that satisfies the task.
- Run
npm run checkwhen validation is required or practical. - Update docs and CHANGELOG.md only when behavior, public contracts, or release-relevant metadata changed.
| Guide | Path |
|---|---|
| Agent rules | AGENTS.md |
| Claude Code | CLAUDE.md |
| Codex | CODEX.md |
| Copilot | COPILOT.md |
| Jules | JULES.md |
| Grok | GROK.md |
| Roadmap | ROADMAP.md |
| Todo | TODO.md |
| Changelog | CHANGELOG.md |
| Security | SECURITY.md |
Astro-native components for the Spectre UI design system. Drop Spectre components into any Astro project — SSR, SSG, or hybrid — without writing CSS, redefining tokens, or reimplementing recipe logic.
Contributing | Code of Conduct | Changelog | Roadmap | Security Policy
- Forty-seven ready-to-use Astro components — alerts, avatars, badges, buttons, cards, app shell layout, forms, navigation, overlays, feedback, pricing, ratings, and testimonials
- SSR-safe by default — deterministic markup, no client-side JavaScript, stable accessibility wiring
- Thin wrapper pattern — styling comes entirely from
@phcdevworks/spectre-ui; this package adds Astro slots, typed props, and framework ergonomics - Re-exported recipe helpers — use the same class functions the components use, directly from your Astro frontmatter or TypeScript
- Astro-native component delivery for Spectre UI recipes and classes
- Astro-friendly, SSR-safe component interfaces and composition patterns
- Type-safe framework bindings for the upstream Spectre UI contract
- Adapter-level ergonomics that make
@phcdevworks/spectre-uistraightforward to consume in Astro projects - A reference implementation for future Spectre framework adapters
Golden rule: bind the upstream Spectre UI contract for Astro, do not redefine it.
- Design values or token meaning —
@phcdevworks/spectre-tokens - Core CSS, utilities, or class recipe logic —
@phcdevworks/spectre-ui - Local styling systems that diverge from the shared Spectre contract
Use @phcdevworks/spectre-ui-astro when:
- you are building an Astro project and want Spectre UI components as first-class Astro components
- you need SSR-safe, type-safe component interfaces that bind the upstream
@phcdevworks/spectre-uirecipe contract without reimplementing it - you want to compose with Spectre's shared recipe helpers from TypeScript in an Astro project
Do not use this package when:
- you are using a different framework (React, Vue, Svelte, etc.) — this package is Astro-only
- you want to define custom tokens or override Spectre's design values — that
belongs in
@phcdevworks/spectre-tokens - you want to add or change class recipes or CSS utilities — that belongs in
@phcdevworks/spectre-ui - you need a framework-agnostic styling contract — consume
@phcdevworks/spectre-uidirectly
npm install @phcdevworks/spectre-ui-astro @phcdevworks/spectre-ui@phcdevworks/spectre-ui is a required peer dependency. It owns the CSS, class
recipes, and design system behavior that powers every component in this package.
Install this package inside an Astro project; astro is also a peer dependency
supplied by the consuming app.
Spectre no longer ships a Tailwind CSS integration. Use the precompiled
@phcdevworks/spectre-ui CSS entry points and recipe helpers shown below.
If your project works with Spectre design tokens directly:
npm install @phcdevworks/spectre-tokensThis package ships no CSS. Add the Spectre UI stylesheet once in your Astro layout:
---
// src/layouts/BaseLayout.astro
import '@phcdevworks/spectre-ui/index.css'
interface Props {
title?: string
}
const { title = 'My Astro site' } = Astro.props
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>All Spectre components pick up the stylesheet through the layout. Do not import
it per-component — CSS ownership stays with @phcdevworks/spectre-ui.
---
import BaseLayout from '../layouts/BaseLayout.astro'
import {
SpAccordion,
SpAccordionItem,
SpAlert,
SpAvatar,
SpBadge,
SpButton,
SpBreadcrumb,
SpCard,
SpCardBleed,
SpCarousel,
SpCarouselIndicator,
SpCarouselSlide,
SpCheckbox,
SpChoiceCard,
SpContainer,
SpDatepicker,
SpDay,
SpDisplay,
SpDivider,
SpDropdown,
SpDropdownDivider,
SpDropdownHeader,
SpDropdownItem,
SpDropdownMenu,
SpExternalAuthButton,
SpFieldset,
SpFileInput,
SpFooter,
SpFooterChip,
SpFooterDivider,
SpFooterHeading,
SpFooterLink,
SpFooterLinks,
SpFooterText,
SpGrid,
SpHeading,
SpIcon,
SpIconBox,
SpInput,
SpInputGroup,
SpInputGroupAddon,
SpLabel,
SpLead,
SpLink,
SpListGroup,
SpListGroupItem,
SpLogoCloud,
SpLogoCloudItem,
SpModal,
SpNav,
SpNavItem,
SpNavLinks,
SpOffcanvas,
SpPagination,
SpPopover,
SpPricingCard,
SpProgress,
SpProse,
SpQuote,
SpRadio,
SpRange,
SpRating,
SpSection,
SpSectionHead,
SpSelect,
SpSidebar,
SpSidebarGroup,
SpSidebarHeader,
SpSidebarLink,
SpSidebarToggle,
SpSkeleton,
SpSpinner,
SpStack,
SpStat,
SpStepper,
SpSurface,
SpSwitch,
SpTableRow,
SpTabPanel,
SpTable,
SpTabs,
SpTag,
SpTestimonial,
SpText,
SpTextarea,
SpToast,
SpTooltip
} from '@phcdevworks/spectre-ui-astro'
---
<BaseLayout title="My page">
<SpCard variant="elevated">
<SpIconBox variant="primary" size="md">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2l7 4v12l-7 4-7-4V6l7-4z" fill="currentColor" />
</svg>
</SpIconBox>
<SpBadge variant="success" size="sm">Stable</SpBadge>
<h2>Build faster with Spectre</h2>
<SpButton variant="primary" size="lg">Get started</SpButton>
</SpCard>
<SpInput
id="email"
label="Email"
name="email"
type="email"
placeholder="you@example.com"
helperText="We will never share your email."
/>
<SpPricingCard featured>
<h3 slot="header">Pro</h3>
<span slot="price">$29/mo</span>
<span slot="description">For growing teams.</span>
<SpButton variant="primary" fullWidth>Choose plan</SpButton>
</SpPricingCard>
</BaseLayout>All components are SSR-safe. Styling comes from the upstream Spectre UI
stylesheet — this package adds no local CSS. Every component accepts a class
prop for additional classes and spreads unknown props onto the root element.
SpSidebar is the one exception to "no client-side JavaScript": it owns the
toggle/backdrop-close interaction for its off-canvas drawer behavior, rendering
closed by default with no layout shift on hydration.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
ButtonVariant |
— | Visual style: "primary" "secondary" "ghost" "inverse" "accentOutline" "action" and the other upstream variants |
size |
ButtonSize |
— | Size: "sm" "md" "lg" |
as |
"button" | "a" | "span" | "div" | "li" |
"button" |
Rendered element |
href |
string |
— | URL when as="a" |
type |
"button" | "submit" | "reset" |
"button" |
Button type (button elements only) |
disabled |
boolean |
— | Disables the element; suppresses navigation on anchors |
loading |
boolean |
— | Loading state; implies disabled |
fullWidth |
boolean |
— | Stretches to fill its container |
iconOnly |
boolean |
— | Removes text padding for icon-only buttons |
pill |
boolean |
— | Fully rounded corners |
compact |
boolean |
— | Shrinks the visible box below the min touch target; an invisible ::after preserves the accessible hit area |
hovered |
boolean |
— | Force-applies hover styling |
focused |
boolean |
— | Force-applies focus styling |
active |
boolean |
— | Force-applies active styling |
aria-label |
string |
— | Accessible label |
tabindex |
number |
— | Tab index override |
class |
string |
— | Additional CSS classes |
<SpButton variant="primary" size="lg">Get started</SpButton>
<SpButton variant="ghost" as="a" href="/docs">Read docs</SpButton>
<SpButton variant="primary" type="submit">Save</SpButton>
<SpButton variant="primary" loading>Saving…</SpButton>
<SpButton variant="secondary" as="a" href="/item" disabled>Unavailable</SpButton>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
CardVariant |
— | Visual style: "elevated" "outline" "flat" "ghost" |
as |
"div" | "section" | "article" | "aside" | "a" | "button" | "li" | … |
"div" |
Rendered element |
interactive |
boolean |
— | Adds hover/focus styles; adds role="button" for non-native elements |
padded |
boolean | "sm" | "md" | "lg" |
— | Applies inner padding |
accent |
CardAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
CardAccentColor |
"brand" |
Rail color, used when accent is set |
fullHeight |
boolean |
— | Stretches to full container height |
disabled |
boolean |
— | Disables the card; suppresses navigation on anchors |
loading |
boolean |
— | Loading state |
href |
string |
— | URL when as="a" |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpCard variant="elevated">
<h2>Card title</h2>
<p>Card content goes here.</p>
</SpCard>
<!-- Semantic article markup -->
<SpCard variant="outline" as="article">
<h2>Blog post title</h2>
</SpCard>
<!-- Linked card -->
<SpCard variant="elevated" as="a" href="/post/1" interactive aria-label="Read post">
<h2>Clickable card</h2>
</SpCard>
<!-- Accent rail -->
<SpCard variant="outline" accent="left" accentColor="success">
<h2>Highlighted card</h2>
</SpCard>The default slot renders any child content. Every component with an accent rail
(card, badge, testimonial, pricing card, nav, footer, modal, toast, tooltip,
dropdown menu, list group) also accepts accentColor="accent".
SpCard does not have a dedicated bleed slot. For a child (media, a flush
internal surface) that should run through the card's padding on one or more
edges, style it directly with the re-exported getCardBleedClasses helper:
---
import { SpCard, getCardBleedClasses } from '@phcdevworks/spectre-ui-astro'
const bleedClass = getCardBleedClasses({ edges: 'top', padded: 'md' })
---
<SpCard padded="md">
<img class={bleedClass} src="/media/hero.jpg" alt="" />
<p>Card body content, still padded normally.</p>
</SpCard>| Prop | Type | Default | Description |
|---|---|---|---|
maxWidth |
ContainerMaxWidth |
— | "prose" bounds content to a readable line length; "wide" widens it |
padding |
ContainerPadding |
— | Inline padding step: "sm" "md" "lg"; omission keeps the default |
as |
"div" | "section" | "main" | "article" | "aside" |
"div" |
Rendered element |
id |
string |
— | Element id |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpContainer>
<p>Centered, max-width content.</p>
</SpContainer>
<SpContainer as="main" aria-label="Main content">
<p>Semantic main wrapper.</p>
</SpContainer>
<SpContainer maxWidth="prose">
<p>Bounded to a readable line length.</p>
</SpContainer>The default slot renders any child content.
| Prop | Type | Default | Description |
|---|---|---|---|
direction |
StackDirection |
"vertical" |
"vertical" | "horizontal" |
basis |
StackBasis |
— | "sidebar" gives the stack a fixed sidebar width |
align |
StackAlign |
"center" |
"center" | "stretch" cross-axis alignment |
gap |
StackGap |
"md" |
"sm" | "md" | "lg" spacing between items |
as |
"div" | "section" | "ul" | "ol" | "nav" |
"div" |
Rendered element |
id |
string |
— | Element id |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpStack>
<p>Item one</p>
<p>Item two</p>
</SpStack>
<SpStack direction="horizontal" as="nav" aria-label="Primary">
<a href="/">Home</a>
<a href="/about">About</a>
</SpStack>
<SpStack direction="horizontal" basis="sidebar">
<p>Fixed-width sidebar-shaped flex child.</p>
</SpStack>
<SpStack direction="horizontal" align="stretch">
<SpSidebar>...</SpSidebar>
<main>Stretches to match the sidebar's height.</main>
</SpStack>
<SpStack gap="lg">
<p>Item one</p>
<p>Item two</p>
</SpStack>The default slot renders any child content.
| Prop | Type | Default | Description |
|---|---|---|---|
spacing |
SectionSpacing |
— | Block padding step: "sm" "md" "lg"; omission keeps the default |
gap |
SectionGap |
— | Stacks direct children with the section gap step: "sm" "md" "lg" |
hero |
"sm" | "md" | "lg" |
— | Asymmetric hero padding (more above than below); replaces spacing |
attached |
boolean |
— | Drops the top padding of a band that belongs to the section above |
as |
"section" | "div" | "article" | "aside" | "main" |
"section" |
Rendered element |
id |
string |
— | Element id |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpSection aria-label="Features">
<SpContainer>
<h2>Features</h2>
</SpContainer>
</SpSection>The default slot renders any child content. spacing and gap also accept the
"xl" "2xl" "3xl" "4xl" steps from @phcdevworks/spectre-ui 5.4.0, as do
the SpStack and SpGrid gaps and the SpContainer padding.
The heading block at the top of a section. The eyebrow and title slots fill
the title part; the default slot (the lead) and the actions slot fill the lead
part. Each part renders only when one of its slots is populated. Typography
comes from SpText, SpHeading, and SpLead.
| Prop | Type | Default | Description |
|---|---|---|---|
layout |
"stacked" | "split" |
"stacked" |
split puts the title left and the lead right from the lg breakpoint |
align |
"start" | "center" |
"start" |
Stacked layout only; center centres the head at prose width |
as |
"div" | "header" | "hgroup" |
"div" |
Rendered element |
aria-label |
string |
— | Accessible label; on a div it also sets role="group" |
class |
string |
— | Additional CSS classes |
<SpSectionHead layout="split">
<SpText slot="eyebrow" as="span" transform="uppercase" size="sm">Services</SpText>
<SpHeading slot="title" level="h2">What we build</SpHeading>
<SpLead>Design systems, web platforms, and the tooling between them.</SpLead>
<SpButton slot="actions" variant="accentOutline">See the work</SpButton>
</SpSectionHead>| Prop | Type | Default | Description |
|---|---|---|---|
as |
"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" |
"p" |
Rendered element |
size |
TextSize |
— | Upstream type scale |
variant |
TextVariant |
— | Upstream color role, including "onInverse"/"onInverseMuted" for on-dark surfaces |
family |
TextFamily |
— | Upstream font family |
transform |
TextTransform |
— | "none" | "uppercase" | "lowercase" | "capitalize" |
weight |
TextWeight |
— | 400–900; overrides the weight the size preset carries |
leading |
TextLeading |
— | "none" "tight" "snug" "normal" "relaxed" "loose"; overrides line height |
fontSize |
TextFontSize |
— | Numeric scale (12–128); sets only the font size, keeping the preset's leading |
tracking |
TextTracking |
— | "tightest"–"widest" letter spacing |
decoration |
TextDecoration |
— | "thin" | "base" | "thick" underline thickness |
underlineOffset |
TextUnderlineOffset |
— | "sm" | "md" | "lg" underline offset |
tabularNums |
boolean |
— | Tabular (fixed-width) numerals |
balance |
boolean |
— | Balanced line wrapping |
id |
string |
— | Element id |
class |
string |
— | Additional CSS classes |
<SpText as="h2" size="2xl" variant="brand">Section heading</SpText>
<SpText variant="muted">Supporting copy.</SpText>
<SpText as="span" transform="uppercase" size="sm">Eyebrow label</SpText>
<SpText as="span" fontSize={48} tracking="tight" tabularNums>1,234</SpText>size takes 7xl, 8xl, and 9xl as well. Since spectre-ui 5.6.0 the
lg–6xl presets follow the Tailwind-aligned font scale (for example, lg is
18px), so use the next size up to keep a previous rendered size.
The default slot renders any child content. SpText maps directly to
getTextClasses; use its as prop to render any heading level or p/span
when text-recipe styling is appropriate. Use SpHeading for the dedicated
heading recipe. variant also takes the "accent", "onSurfaceAccent",
"onInverseBrand", and "onInverseAccent" roles.
| Prop | Type | Default | Description |
|---|---|---|---|
bordered |
boolean |
— | Applies a right border |
as |
"aside" | "div" | "nav" |
"aside" |
Rendered element for the sidebar itself |
id |
string |
— | Element ID for the sidebar |
aria-label |
string |
— | Accessible label for the sidebar |
toggleLabel |
string |
"Toggle sidebar" |
Accessible label for the hamburger toggle button |
hideToggle |
boolean |
false |
Suppresses the built-in toggle button, e.g. when placing SpSidebarToggle inside SpNav instead |
class |
string |
— | Additional CSS classes for the sidebar |
SpSidebar is the first adapter component to own interactive state. It renders
a wrapper element with data-sidebar-open="false" (closed by default,
SSR-safe), a hamburger toggle button, a backdrop element
(getSidebarBackdropClasses), and the sidebar element itself
(getSidebarClasses). Below breakpoints.md (768px), upstream
@phcdevworks/spectre-ui CSS renders the sidebar off-canvas; toggling the
button or tapping the backdrop flips data-sidebar-open, which upstream CSS
reacts to. Above breakpoints.md, the sidebar docks inline and the toggle has
no visible effect, matching the upstream CSS contract.
To integrate the hamburger toggle into a top SpNav bar instead of leaving it
next to the off-canvas sidebar, pass hideToggle to SpSidebar and render
SpSidebarToggle inside SpNav, pointing its for prop at the sidebar's id:
<SpNav bordered sticky fullWidth>
<SpSidebarToggle for="docs-sidebar" />
</SpNav>
<SpSidebar id="docs-sidebar" hideToggle bordered aria-label="Primary">
...
</SpSidebar>Both buttons (the built-in one and SpSidebarToggle) share the same
.sp-sidebar-toggle styling, so they always match the current theme via
--sp-component-sidebar-toggle-* tokens.
Build sidebar nav groups in the default slot using SpSidebarLink for each link
and the re-exported getSidebarHeaderClasses helper for section headers, since
section headers are consumer-driven.
---
import { SpSidebar, SpSidebarLink, getSidebarHeaderClasses } from '@phcdevworks/spectre-ui-astro'
const headerClass = getSidebarHeaderClasses()
---
<SpSidebar bordered aria-label="Primary">
<span class={headerClass}>Guides</span>
<SpSidebarLink href="/" active>Home</SpSidebarLink>
<SpSidebarLink href="/about">About</SpSidebarLink>
<SpSidebarLink href="/about/team" level="child">Team</SpSidebarLink>
</SpSidebar>| Prop | Type | Default | Description |
|---|---|---|---|
href |
string |
— | Link target; suppressed when disabled |
active |
boolean |
false |
Marks the link current (aria-current="page") |
disabled |
boolean |
false |
Suppresses href, sets aria-disabled and tabindex=-1 |
hovered |
boolean |
false |
Forces hover-state classes |
focused |
boolean |
false |
Forces focus-state classes |
level |
SidebarLinkLevel |
"parent" |
"parent" | "child" — indents nested links |
id |
string |
— | Element id |
title |
string |
— | Native title attribute |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
SpSidebarLink renders a single <a>, backed by getSidebarLinkClasses. The
level option indents nested links under a section header.
| Prop | Type | Default | Description |
|---|---|---|---|
for |
string |
— | Required id of the SpSidebar to control |
label |
string |
"Toggle sidebar" |
Accessible label for the toggle button |
hovered |
boolean |
— | Forces the hover look |
focused |
boolean |
— | Forces the focus look |
class |
string |
— | Additional CSS classes for the toggle button |
SpSidebarToggle renders a button that can live outside SpSidebar, such as
inside SpNav. Its for prop must match the target sidebar's id. The sidebar
wiring binds again after Astro client-side navigation so newly swapped sidebar
shells remain interactive.
| Prop | Type | Default | Description |
|---|---|---|---|
columns |
GridColumns |
1 |
1 | 2 | 3 | 4 | 6 | 12 |
gap |
GridGap |
"md" |
"sm" | "md" | "lg" |
columnGap |
GridGap |
— | Column-axis gap override |
rowGap |
GridGap |
— | Row-axis gap override |
span |
GridSpan | GridSpanOptions |
— | Column span for a grid item: a single value or { base?, md?, lg? } per breakpoint |
offset |
GridOffset | GridOffsetOptions |
— | Column offset for a grid item: 0-11 or { base?, md?, lg? } |
colStart |
GridColStart | GridColStartOptions |
— | Explicit starting column line for a grid item: 1-12 or { base?, md?, lg? } |
rowSpan |
GridSpan | GridSpanOptions |
— | Row span for a grid item, same shape as span |
rowOffset |
GridOffset | GridOffsetOptions |
— | Row offset for a grid item, same shape as offset |
order |
GridOrder | GridOrderOptions |
— | Visual order for a grid item: "first" | "last" | "none" | 1-12, or per breakpoint |
align |
GridAlign |
— | "start" | "center" | "end" | "baseline" | "stretch" cross-axis cell alignment |
leadingTracks |
GridLeadingTracksOptions |
— | { weight } — proportional leading-track sizing without hand-rolled grid-template-columns |
fixedTracks |
GridFixedTracksOptions |
— | { count } — fixed track count for custom track layouts |
explicitTemplate |
GridExplicitTemplateOptions |
— | { template, weight? } — named asymmetric column template ("edge-fluid-edge" | "label-fluid-fluid"); mutually exclusive with columns/leadingTracks/fixedTracks |
as |
"div" | "section" | "ul" | "ol" |
"div" |
Rendered element |
id |
string |
— | Element id |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpGrid columns={3} gap="lg">
<SpCard>One</SpCard>
<SpCard>Two</SpCard>
<SpCard>Three</SpCard>
</SpGrid>
<SpGrid columns={12} gap="lg">
<SpCard span={{ base: 'full', md: 6, lg: 4 }}>One</SpCard>
<SpCard span={{ base: 'full', md: 6, lg: 8 }}>Two</SpCard>
</SpGrid>
<SpGrid leadingTracks={{ weight: 2 }} fixedTracks={{ count: 2 }} gap="lg">
<SpCard offset={2} rowSpan={2}>Leads two fixed trailing tracks</SpCard>
<SpCard order="last">Second track</SpCard>
</SpGrid>
<SpGrid explicitTemplate={{ template: "edge-fluid-edge" }} gap="md">
<SpIconBox>Logo</SpIconBox>
<SpNav>Nav links</SpNav>
<SpButton>CTA</SpButton>
</SpGrid>
<SpGrid columns={3} align="center">
<SpCard>One</SpCard>
<SpCard>Two, taller content</SpCard>
<SpCard>Three</SpCard>
</SpGrid>The default slot renders any child content. span, offset, rowSpan,
rowOffset, and order are set on individual grid items (not the SpGrid
wrapper) and each accepts either a single value or a per-breakpoint
{ base?, md?, lg? } object; they map directly to the matching getGridClasses
option of the same name. leadingTracks, fixedTracks, and explicitTemplate
are set on the SpGrid wrapper itself and map to getGridClasses's options of
the same name. explicitTemplate selects a named, finite asymmetric column
shape ("edge-fluid-edge" for a logo/nav/CTA row, "label-fluid-fluid" for a
fixed leading label column plus two differently-weighted fluid columns) for
layouts that columns/span/ leadingTracks/fixedTracks cannot express, and
is mutually exclusive with those column-sizing options.
SpInput renders a labeled input group: wrapper, optional label, input,
optional helper text, and optional error message.
SpInput requires an explicit id whenever label, helperText, or
errorMessage is present. This is an SSR invariant — without a stable id, the
for/aria-describedby associations would be nondeterministic. The component
throws at render time if the requirement is violated.
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string |
— | Required when using label, helperText, or errorMessage |
label |
string |
— | Renders a <label> associated with the input |
helperText |
string |
— | Renders helper text below the input |
errorMessage |
string |
— | Renders an error message; suppresses helperText |
state |
InputState |
— | "default" "success" "error" "disabled" "loading" |
size |
InputSize |
— | Size: "sm" "md" "lg" |
fullWidth |
boolean |
— | Stretches input to fill its container |
pill |
boolean |
— | Fully rounded corners |
disabled |
boolean |
— | Disables the input |
loading |
boolean |
— | Loading state |
as |
"div" | "form" | "fieldset" | … |
"div" |
Rendered wrapper element |
class |
string |
— | Additional CSS classes on the <input> element |
…rest |
— | — | Any HTML input attribute (type, name, placeholder, required, etc.) |
<!-- Standalone — no label, no id required -->
<SpInput type="search" placeholder="Search…" />
<!-- Labeled with helper text — id required -->
<SpInput
id="email"
label="Email"
name="email"
type="email"
placeholder="you@example.com"
helperText="We will never share your email."
/>
<!-- Validation error — errorMessage suppresses helperText -->
<SpInput
id="password"
label="Password"
type="password"
state="error"
errorMessage="Password must be at least 8 characters."
/>
<!-- Disabled field -->
<SpInput
id="api-key"
label="API Key"
value="••••••••"
state="disabled"
disabled
/>
<!-- Pill shape, small size -->
<SpInput id="search-pill" label="Search" size="sm" pill placeholder="Search…" />SpLabel renders a standalone <label>. Association with a form control is the
consumer's responsibility, the same as plain HTML — pass htmlFor matching the
control's id.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
— | Disabled styling |
required |
boolean |
— | Required-field styling |
htmlFor |
string |
— | Renders the for attribute |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpLabel htmlFor="email">Email</SpLabel>
<SpInput id="email" type="email" />
<SpLabel htmlFor="agree" required>I agree to the terms</SpLabel>SpFieldset renders a <fieldset> with an optional <legend>. The legend only
renders when legend is provided and non-empty.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
— | Disables every control inside the fieldset |
legend |
string |
— | Renders a <legend> with the recipe class |
name |
string |
— | Form field group name |
form |
string |
— | Associates with a <form> by ID |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpFieldset legend="Contact details">
<SpLabel htmlFor="name">Name</SpLabel>
<SpInput id="name" name="name" />
</SpFieldset>
<SpFieldset disabled legend="Read-only section">
<SpInput id="readonly-field" value="Locked" disabled />
</SpFieldset>SpCheckbox renders a native <input type="checkbox">.
| Prop | Type | Default | Description |
|---|---|---|---|
checked |
boolean |
— | Checked state |
disabled |
boolean |
— | Disables the checkbox |
id |
string |
— | Element ID |
name |
string |
— | Form field name |
value |
string |
— | Form field value |
required |
boolean |
— | Marks the field required |
form |
string |
— | Associates with a <form> by ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpLabel htmlFor="agree">I agree to the terms</SpLabel>
<SpCheckbox id="agree" name="terms" />
<SpCheckbox id="newsletter" name="subscribe" checked />
<SpCheckbox id="locked" disabled />SpRadio renders a native <input type="radio">.
| Prop | Type | Default | Description |
|---|---|---|---|
checked |
boolean |
— | Checked state |
disabled |
boolean |
— | Disables the radio |
id |
string |
— | Element ID |
name |
string |
— | Form field name — shared across a group |
value |
string |
— | Form field value |
required |
boolean |
— | Marks the field required |
form |
string |
— | Associates with a <form> by ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpLabel htmlFor="plan-pro">Pro</SpLabel>
<SpRadio id="plan-pro" name="plan" value="pro" checked />
<SpLabel htmlFor="plan-free">Free</SpLabel>
<SpRadio id="plan-free" name="plan" value="free" />SpSelect renders a native <select>. Pass <option> elements as children.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
— | Disables the select |
focused |
boolean |
— | Applies focus styling |
id |
string |
— | Element ID |
name |
string |
— | Form field name |
value |
string |
— | Selected value |
required |
boolean |
— | Marks the field required |
multiple |
boolean |
— | Allows multiple selection |
form |
string |
— | Associates with a <form> by ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpLabel htmlFor="country">Country</SpLabel>
<SpSelect id="country" name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</SpSelect>SpTextarea renders a native <textarea>.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
— | Disables the textarea |
focused |
boolean |
— | Applies focus styling |
id |
string |
— | Element ID |
name |
string |
— | Form field name |
value |
string |
— | Default text content |
placeholder |
string |
— | Placeholder text |
rows |
number |
— | Visible row count |
required |
boolean |
— | Marks the field required |
readonly |
boolean |
— | Prevents editing |
form |
string |
— | Associates with a <form> by ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpLabel htmlFor="bio">Bio</SpLabel>
<SpTextarea id="bio" name="bio" rows={4} placeholder="Tell us about yourself" />| Prop | Type | Default | Description |
|---|---|---|---|
variant |
AlertVariant |
"info" |
Visual style: "info" "success" "warning" "danger" "neutral" "brand" |
size |
AlertSize |
"md" |
Size: "sm" "md" "lg" |
as |
"div" | "section" | "aside" | "article" |
"div" |
Rendered element |
dismissed |
boolean |
— | Applies dismissed state styling |
dismissible |
boolean |
— | Reserves and renders a dismiss control |
dismissLabel |
string |
"Dismiss" |
Accessible label for the dismiss control |
interactive |
boolean |
— | Adds hover/focus styles and tabindex="0" |
fullWidth |
boolean |
— | Stretches to full width |
disabled |
boolean |
— | Disables the alert |
loading |
boolean |
— | Loading state (also sets disabled behavior) |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpAlert variant="success">Your changes have been saved.</SpAlert>
<SpAlert variant="warning" size="sm">Session expires soon.</SpAlert>
<SpAlert variant="danger" dismissed>This alert has been dismissed.</SpAlert>
<SpAlert variant="brand" dismissible><span slot="icon">i</span>New feature available.</SpAlert>
<SpAlert variant="info" as="aside" aria-label="Info notice">Read the docs.</SpAlert>| Prop | Type | Default | Description |
|---|---|---|---|
shape |
AvatarShape |
"circle" |
Shape: "circle" "square" |
size |
AvatarSize |
"md" |
Size: "xs" "sm" "md" "lg" "xl" |
as |
"div" | "span" | "figure" | "a" | "button" |
"div" |
Rendered element |
interactive |
boolean |
— | Adds hover/focus styles |
disabled |
boolean |
— | Disables the avatar |
loading |
boolean |
— | Loading state |
fullWidth |
boolean |
— | Stretches to full width |
placeholder |
boolean |
— | Applies placeholder styling |
href |
string |
— | URL when as="a" |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpAvatar size="lg">
<img src="/avatars/jane.jpg" alt="Jane Doe" />
</SpAvatar>
<SpAvatar shape="square" size="sm" placeholder>
JD
</SpAvatar>
<SpAvatar as="a" href="/profile" interactive>
<img src="/avatars/jane.jpg" alt="View profile" />
</SpAvatar>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
BadgeVariant |
— | Visual style: "primary" "success" "warning" "danger" "info" "inverse" |
size |
BadgeSize |
— | Size: "sm" "md" "lg" |
as |
"span" | "div" | "a" | "button" | "li" | "time" | "mark" |
"span" |
Rendered element |
interactive |
boolean |
— | Adds hover/focus styles |
fullWidth |
boolean |
— | Stretches to full width |
dot |
boolean |
— | Text-free notification dot (e.g. overlaid on an avatar) |
accentRail |
BadgeAccentRailEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentRailColor |
BadgeAccentRailColor |
"brand" |
Rail color, used when accentRail is set |
disabled |
boolean |
— | Disables the badge |
loading |
boolean |
— | Loading state |
href |
string |
— | URL when as="a" |
datetime |
string |
— | Datetime value when as="time" |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpBadge variant="success">Active</SpBadge>
<SpBadge variant="warning" size="sm">Beta</SpBadge>
<SpBadge variant="primary" as="a" href="/changelog" interactive>New</SpBadge>
<SpBadge variant="danger" as="time" datetime="2025-01-01">Jan 2025</SpBadge>
<SpBadge accentRail="left" accentRailColor="success">Verified</SpBadge>accentRail/accentRailColor are named distinctly from variant because
variant="accent" already names badge's single-tone brand-accent fill; the rail
is an unrelated, additive edge decoration.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
IconBoxVariant |
— | Color: "primary" "success" "warning" "danger" "info" |
size |
IconBoxSize |
— | Size: "sm" "md" "lg" |
iconSize |
IconBoxIconSize |
— | Glyph size "xs"–"3xl"; omitted follows the box's font size |
as |
"span" | "div" | "i" | "a" | "button" | "li" |
"span" |
Rendered element |
pill |
boolean |
— | Fully rounded corners |
interactive |
boolean |
— | Adds hover/focus styles |
disabled |
boolean |
— | Disables the icon box |
loading |
boolean |
— | Loading state |
href |
string |
— | URL when as="a" |
aria-label |
string |
— | Accessible label (use when the icon conveys standalone meaning) |
class |
string |
— | Additional CSS classes |
<!-- Decorative icon — mark the icon aria-hidden -->
<SpIconBox variant="primary" size="md">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2l7 4v12l-7 4-7-4V6l7-4z" fill="currentColor" />
</svg>
</SpIconBox>
<!-- Standalone meaning — label the component -->
<SpIconBox variant="success" size="sm" aria-label="Success">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path fill="currentColor" d="M20 6 9 17l-5-5" />
</svg>
</SpIconBox>Use aria-hidden="true" on the icon when surrounding context already describes
it. Use aria-label on the component when the icon box conveys standalone
meaning. SpIcon is decorative by default, so it drops straight in:
<SpIconBox variant="primary" iconSize="lg">
<SpIcon icon={Rocket} />
</SpIconBox>SpIcon renders an inline SVG from the icon sets @phcdevworks/spectre-ui
ships: pass the imported icon export itself as icon. Only the icons a page
imports are loaded, and the SVG renders on the server with no client script.
| Prop | Type | Default | Description |
|---|---|---|---|
icon |
SpIconData |
— | Required. Any export from @phcdevworks/spectre-ui/icons or /icons/* |
size |
IconSize |
— | "xs"–"3xl" through getIconSizeClasses; omitted follows the font size |
label |
string |
— | Accessible name; sets role="img". Omitted renders the icon aria-hidden |
title |
string |
— | Renders an SVG <title> tooltip |
class |
string |
— | Additional CSS classes |
---
import { SpIcon } from '@phcdevworks/spectre-ui-astro'
import { Search } from '@phcdevworks/spectre-ui/icons'
import { faHouse } from '@phcdevworks/spectre-ui/icons/fa-solid'
import { siGithub } from '@phcdevworks/spectre-ui/icons/brands'
---
<SpIcon icon={Search} />
<SpIcon icon={faHouse} size="lg" />
<SpIcon icon={siGithub} label="GitHub" />Lucide icons draw as 2px outlines in currentColor; Material Design Icons
(/icons/mdi), Font Awesome (/icons/fa-solid, /icons/fa-regular,
/icons/fa-brands), and Simple Icons brand logos (/icons/brands) fill with
currentColor. The output matches spectre-components <sp-icon> for the same
icon.
Named slots map to the structural sections of the pricing card. Slot wrappers render only when their slot is populated — empty slots produce no markup.
| Prop | Type | Default | Description |
|---|---|---|---|
featured |
boolean |
— | Highlights the card as the recommended tier |
fullHeight |
boolean |
— | Stretches to full container height |
accent |
PricingCardAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
PricingCardAccentColor |
"brand" |
Rail color, used when accent is set |
interactive |
boolean |
— | Adds hover/focus styles |
disabled |
boolean |
— | Disables the card |
loading |
boolean |
— | Loading state |
as |
"div" | "section" | "article" | … |
"div" |
Rendered element |
class |
string |
— | Additional CSS classes |
| Slot | Description |
|---|---|
header |
Plan name or heading |
badge |
Tag or label (e.g., "Popular") |
price |
Price string or element |
description |
Short plan description |
| (default) | Feature list or body content |
footer |
CTA button or footer action |
<SpPricingCard featured>
<h3 slot="header">Pro</h3>
<span slot="badge">Popular</span>
<span slot="price">$29/mo</span>
<span slot="description">For growing teams and businesses.</span>
<ul>
<li>Unlimited projects</li>
<li>Advanced analytics</li>
<li>Priority support</li>
</ul>
<SpButton slot="footer" variant="primary" fullWidth>Choose Pro</SpButton>
</SpPricingCard>Renders a star rating. Stars are built from value/max. Provide a custom star
SVG via the star-icon slot. The star container is aria-hidden="true" —
always pass aria-label for screen readers.
| Prop | Type | Default | Description |
|---|---|---|---|
value |
number |
0 |
Number of filled stars |
max |
number |
5 |
Total star count |
size |
RatingSize |
— | Size |
interactive |
boolean |
— | Adds hover/focus styles |
disabled |
boolean |
— | Disables the rating |
loading |
boolean |
— | Loading state |
fullWidth |
boolean |
— | Stretches to fill container |
pill |
boolean |
— | Fully rounded corners |
as |
"div" | "span" | "section" | … |
"div" |
Rendered element |
aria-label |
string |
— | Screen-reader description of the rating value |
class |
string |
— | Additional CSS classes |
| Slot | Description |
|---|---|
star-icon |
Custom star icon. Rendered once per star; filled/empty styling comes from the star wrapper class. Defaults to ★. |
| (default) | Optional text shown after the stars (e.g., "4.8 out of 5") |
<!-- Basic rating -->
<SpRating value={4} max={5} aria-label="4 out of 5 stars" />
<!-- With visible text -->
<SpRating value={4} max={5} aria-label="4 out of 5 stars">
4.0 out of 5
</SpRating>
<!-- Custom star icon -->
<SpRating value={3} max={5} aria-label="3 out of 5">
<svg slot="star-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2l3 6.5 7 1-5 4.9 1.2 7L12 18l-6.2 3.4 1.2-7L2 9.5l7-1z" fill="currentColor" />
</svg>
</SpRating>Named slots map to the structural sections of the testimonial. Slot wrappers render only when their slot is populated.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
"elevated" | "flat" | "outline" | "ghost" |
"elevated" |
Visual style |
fullHeight |
boolean |
— | Stretches to full container height |
accent |
TestimonialAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
TestimonialAccentColor |
"brand" |
Rail color, used when accent is set |
interactive |
boolean |
— | Adds hover/focus styles |
disabled |
boolean |
— | Disables the testimonial |
loading |
boolean |
— | Loading state |
as |
"div" | "section" | "article" | "blockquote" | … |
"div" |
Rendered element |
class |
string |
— | Additional CSS classes |
| Slot | Description |
|---|---|
quote |
Quotation text |
author-image |
Author avatar or image element |
author-name |
Author display name |
author-title |
Author job title or affiliation |
<SpTestimonial as="blockquote">
<p slot="quote">
"Spectre UI cut our Astro prototype time in half."
</p>
<img
slot="author-image"
src="/avatars/profile.jpg"
alt="Product team lead"
width="40"
height="40"
/>
<span slot="author-name">Product team lead</span>
<span slot="author-title">A production consumer</span>
</SpTestimonial>A blockquote or pull quote on getQuoteClasses. It renders a <figure> with
the default slot inside a <blockquote>, and a <figcaption> built from the
avatar, name, role, and organization slots when any of them is
populated. Role and organization share one line, comma-separated.
| Prop | Type | Default | Description |
|---|---|---|---|
size |
QuoteSize |
"md" |
"sm" to an "xl" statement quote |
surface |
"plain" | "subtle" | "inverse" |
"plain" |
subtle/inverse add a padded panel; inverse switches to on-dark text |
accent |
"edge" | "mark" | "none" |
"edge" |
Leading rail, decorative quote mark, or the neutral border rule |
accentColor |
QuoteAccentColor |
"accent" |
Rail and mark tone; "action" follows the theme's data-spectre-action |
align |
"start" | "center" |
"start" |
Text alignment |
cite |
string |
— | Source URL, set on the <blockquote> |
class |
string |
— | Additional CSS classes |
<SpQuote size="lg" accent="mark">
<p>Spectre let us ship the redesign without touching a single hex value.</p>
<SpAvatar slot="avatar">PL</SpAvatar>
<span slot="name">Product lead</span>
<span slot="role">Engineering</span>
<span slot="organization">A production consumer</span>
</SpQuote>A non-interactive status indicator. Renders as <div role="status"> with a
default aria-label of "Loading".
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
SpinnerVariant |
— | Color variant: "primary" "secondary" "success" "warning" "danger" "info" "neutral" "accent" "cta" |
size |
SpinnerSize |
"md" |
Size: "sm" "md" "lg" |
disabled |
boolean |
— | Disabled state |
loading |
boolean |
— | Loading state (also sets disabled behavior) |
aria-label |
string |
"Loading" |
Accessible label for the spinner |
id |
string |
— | Element ID |
class |
string |
— | Additional CSS classes |
<SpSpinner />
<SpSpinner variant="primary" size="lg" />
<SpSpinner loading aria-label="Saving changes" />A loading placeholder. It renders with aria-hidden="true", so put
aria-busy="true" on the region it stands in for.
| Prop | Type | Default | Description |
|---|---|---|---|
shape |
"text" | "rect" | "circle" |
"text" |
rect fills the box you size through class |
animated |
boolean |
— | Shimmer sweep; it stops under prefers-reduced-motion: reduce |
as |
"div" | "span" |
"div" |
Rendered element |
id |
string |
— | Element ID |
class |
string |
— | Additional CSS classes |
<div aria-busy="true">
<SpSkeleton shape="circle" animated />
<SpSkeleton />
</div>A single metric for stat rails, metric cards, and case-study figures. The
default slot is the figure; the unit slot renders inside it in the surface's
accent colour, and the label slot renders beneath it.
| Prop | Type | Default | Description |
|---|---|---|---|
size |
"sm" | "md" | "lg" | "xl" |
"md" |
Type scale of the figure |
align |
"start" | "center" | "end" |
"start" |
Alignment |
surface |
"page" | "surface" | "inverse" |
"page" |
What the stat sits on, which picks the text roles |
unitTone |
"accent" | "action" |
"accent" |
Unit colour; "action" follows the theme's action colour |
aria-label |
string |
— | Accessible label; also sets role="group" |
class |
string |
— | Additional CSS classes |
<SpStat size="xl" surface="inverse">
98<span slot="unit">%</span>
<span slot="label">Lighthouse performance</span>
</SpStat>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
TagVariant |
"default" |
Visual style: "default" "primary" "secondary" "success" "warning" "danger" "info" "neutral" "accent" "cta" "outline" "ghost" |
size |
TagSize |
— | Size: "sm" "md" "lg" |
as |
"span" | "div" | "li" | "a" | "button" |
"span" |
Rendered element |
dismissible |
boolean |
— | Applies dismissible styling |
selected |
boolean |
— | Applies selected state and aria-pressed |
interactive |
boolean |
— | Adds hover/focus styles and tabindex="0" |
fullWidth |
boolean |
— | Stretches to full width |
disabled |
boolean |
— | Disables the tag |
loading |
boolean |
— | Loading state (also sets disabled behavior) |
href |
string |
— | URL when as="a" |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes |
<SpTag>Default</SpTag>
<SpTag variant="primary" size="sm">New</SpTag>
<SpTag variant="success" selected>Active</SpTag>
<SpTag variant="info" dismissible>Removable</SpTag>
<SpTag as="a" href="/docs" variant="neutral" interactive>Docs</SpTag>
<SpTag as="button" variant="primary" disabled>Unavailable</SpTag>| Prop | Type | Default | Description |
|---|---|---|---|
fullWidth |
boolean |
— | Stretches to full width |
mega |
boolean |
— | Anchors the menu to the nearest positioned ancestor (e.g. SpNav) instead of this trigger wrapper, for wide-menu panels that span the nav row rather than tracking trigger width |
viewport |
boolean |
— | Breaks the menu out to the full browser viewport width instead of tracking the trigger or nearest positioned ancestor. Takes precedence over mega when both are set on the menu |
as |
"div" | "nav" |
"div" |
Rendered element |
id |
string |
— | Element ID |
class |
string |
— | Additional CSS classes |
SpDropdown renders the dropdown container only. Build the menu and items in
the default slot using the re-exported getDropdownMenuClasses and
getDropdownItemClasses helpers, since open/closed state and per-item
active/disabled/hover/focus state are consumer-driven. Pair mega (or
viewport) here with the matching option on the getDropdownMenuClasses call
that styles the menu panel — SpNavItem's mega/viewport props do this
pairing for you in dropdown mode. getDropdownMenuClasses also accepts
accent/accentColor for an optional decorative rail on the menu panel.
---
import { SpDropdown, getDropdownMenuClasses, getDropdownItemClasses } from '@phcdevworks/spectre-ui-astro'
const menuClass = getDropdownMenuClasses({ placement: 'bottom-start', open: true })
const itemClass = getDropdownItemClasses()
const activeItemClass = getDropdownItemClasses({ active: true })
---
<SpDropdown>
<button>Options</button>
<div class={menuClass} role="menu">
<a class={activeItemClass} href="/profile" role="menuitem">Profile</a>
<a class={itemClass} href="/settings" role="menuitem">Settings</a>
</div>
</SpDropdown>| Prop | Type | Default | Description |
|---|---|---|---|
bordered |
boolean |
— | Applies a top border |
fullWidth |
boolean |
— | Stretches to full width |
accent |
FooterAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
FooterAccentColor |
"brand" |
Rail color, used when accent is set |
appearance |
FooterAppearance |
"dark" |
Palette: "dark" "light" "system" |
surface |
FooterSurface |
— | Background role: "page" "card" "subtle" "inverse" "hero" "action" |
as |
"footer" | "div" | "section" |
"footer" |
Rendered element |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
<SpFooter bordered>
<p>© 2026 PHCDevworks</p>
</SpFooter>The default slot renders any child content.
Build footer content in the default slot using SpFooterLink and SpFooterChip
for interactive elements, and the re-exported getFooterHeadingClasses,
getFooterTextClasses, getFooterMutedClasses, getFooterLinksClasses, and
getFooterDividerClasses helpers for the surrounding structure, since section
headings and link lists are consumer-driven — the same pattern as SpNav's
getNavLinksClasses and SpSidebar's getSidebarHeaderClasses.
---
import {
SpFooter,
SpFooterLink,
SpFooterChip,
getFooterHeadingClasses,
getFooterTextClasses,
getFooterMutedClasses,
getFooterLinksClasses,
getFooterDividerClasses,
} from '@phcdevworks/spectre-ui-astro'
const headingClass = getFooterHeadingClasses()
const linksClass = getFooterLinksClasses()
---
<SpFooter bordered>
<p class={getFooterTextClasses()}>© 2026 PHCDevworks</p>
<p class={getFooterMutedClasses()}>All rights reserved.</p>
<hr class={getFooterDividerClasses()} />
<div>
<span class={headingClass}>Product</span>
<nav class={linksClass}>
<SpFooterLink href="/" active>Home</SpFooterLink>
<SpFooterLink href="/docs">Docs</SpFooterLink>
</nav>
</div>
<SpFooterChip>Beta</SpFooterChip>
</SpFooter>| Prop | Type | Default | Description |
|---|---|---|---|
href |
string |
— | Link target; suppressed when disabled |
active |
boolean |
false |
Marks the link current (aria-current="page") |
disabled |
boolean |
false |
Suppresses href, sets aria-disabled and tabindex=-1 |
hovered |
boolean |
false |
Forces hover-state classes |
focused |
boolean |
false |
Forces focus-state classes |
id |
string |
— | Element id |
title |
string |
— | Native title attribute |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
SpFooterLink renders a single <a>, backed by getFooterLinkClasses.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Applies disabled-state classes |
hovered |
boolean |
false |
Forces hover-state classes |
focused |
boolean |
false |
Forces focus-state classes |
id |
string |
— | Element id |
title |
string |
— | Native title attribute |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
SpFooterChip renders a single <span>, backed by getFooterChipClasses.
| Prop | Type | Default | Description |
|---|---|---|---|
open |
boolean |
— | Applies open styling to the overlay and modal, and toggles aria-hidden |
fullWidth |
boolean |
— | Stretches the modal to full width |
accent |
ModalAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
ModalAccentColor |
"brand" |
Rail color, used when accent is set |
as |
"div" | "section" |
"div" |
Rendered element for the modal |
id |
string |
— | Element ID for the modal |
aria-label |
string |
— | Accessible label for the modal |
aria-labelledby |
string |
— | Associates a title element |
aria-describedby |
string |
— | Associates a description element |
class |
string |
— | Additional CSS classes for the modal |
SpModal renders an overlay element (getModalOverlayClasses) wrapping the
modal element (getModalClasses), with role="dialog" and aria-modal="true".
Toggling open is consumer-driven (no client-side JS is included).
<SpModal open aria-labelledby="modal-title">
<h2 id="modal-title">Confirm deletion</h2>
<p>This action cannot be undone.</p>
</SpModal>| Prop | Type | Default | Description |
|---|---|---|---|
bordered |
boolean |
— | Applies a border |
sticky |
boolean |
— | Applies sticky positioning |
fullWidth |
boolean |
— | Stretches to full width |
align |
NavAlign |
— | Aligns content: "start", "center", or "end" |
accent |
NavAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
NavAccentColor |
"brand" |
Rail color, used when accent is set |
as |
"nav" | "div" | "header" | "section" |
"nav" |
Rendered element |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label for the nav landmark |
class |
string |
— | Additional CSS classes |
SpNav renders the nav container only. Build links in the default slot using
the re-exported getNavLinksClasses and getNavLinkClasses helpers, since
per-link active/disabled/hover/focus state is consumer-driven.
---
import { SpNav, getNavLinksClasses, getNavLinkClasses } from '@phcdevworks/spectre-ui-astro'
const linksClass = getNavLinksClasses()
const activeLinkClass = getNavLinkClasses({ active: true })
const linkClass = getNavLinkClasses()
---
<SpNav bordered sticky align="center" aria-label="Main">
<div class={linksClass}>
<a class={activeLinkClass} href="/" aria-current="page">Home</a>
<a class={linkClass} href="/about">About</a>
</div>
</SpNav>| Prop | Type | Default | Description |
|---|---|---|---|
dropdown |
boolean |
— | Renders a dropdown trigger + menu instead of a plain link |
href |
string |
— | Link target when dropdown is not set |
label |
string |
— | Trigger/link text when no content is projected |
open |
boolean |
— | Applies open styling to the menu (dropdown mode only) |
active |
boolean |
— | Current-page link styling; adds aria-current="page" in link mode |
disabled |
boolean |
— | Disabled styling; suppresses href in link mode and disables the trigger in dropdown mode |
hovered / focused |
boolean |
— | Force the matching interaction-state classes (docs and previews) |
placement |
DropdownPlacement |
"bottom-start" |
Menu position (dropdown mode only) |
mega |
boolean |
— | Wide-menu mode (dropdown mode only): anchors the menu to the nearest positioned ancestor and spans its full width instead of tracking trigger width |
viewport |
boolean |
— | Full-viewport-width mode (dropdown mode only): breaks the menu out to the full browser viewport width. Takes precedence over mega when both are set |
accent |
DropdownAccentEdge |
— | Decorative rail edge on the menu panel (dropdown mode only): "top" "right" "bottom" "left" |
accentColor |
DropdownAccentColor |
"brand" |
Rail color, used when accent is set |
id |
string |
— | Element ID |
title |
string |
— | Title attribute |
aria-label |
string |
— | Accessible label for the link or trigger button |
class |
string |
— | Additional CSS classes |
Place SpNavItem inside SpNav alongside plain links. In link mode it renders
an <a> styled with getNavLinkClasses. In dropdown mode it renders a
getDropdownClasses wrapper around a getNavLinkClasses-styled trigger
<button> (data-sp-nav-item-trigger, aria-haspopup, aria-expanded) and a
getDropdownMenuClasses menu panel (data-sp-nav-item-menu) that receives the
default slot. mega and viewport are forwarded to both calls, matching
upstream's paired mega/viewport contract; accent/accentColor are
forwarded to the menu panel only. Toggling open and wiring
click/outside-click/escape behavior is consumer-driven — no client-side JS is
included; use @phcdevworks/spectre-components's sp-nav-item if you want that
behavior built in. Use a trigger named slot to project custom trigger content
instead of label.
---
import { SpNav, SpNavItem } from '@phcdevworks/spectre-ui-astro'
---
<SpNav aria-label="Main">
<SpNavItem href="/">Home</SpNavItem>
<SpNavItem dropdown label="Products" open placement="bottom-end">
<a href="/products/a">Product A</a>
<a href="/products/b">Product B</a>
</SpNavItem>
<SpNavItem dropdown mega label="Solutions" open>
<SpGrid columns={3} gap="lg">
<a href="/solutions/a">Solution A</a>
<a href="/solutions/b">Solution B</a>
<a href="/solutions/c">Solution C</a>
</SpGrid>
</SpNavItem>
</SpNav>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
ToastVariant |
"info" |
Visual style: "info" "success" "warning" "danger" |
dismissed |
boolean |
— | Applies dismissed state styling |
fullWidth |
boolean |
— | Stretches to full width |
accent |
ToastAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
ToastAccentColor |
"brand" |
Rail color, used when accent is set |
as |
"div" | "li" | "section" |
"div" |
Rendered element |
id |
string |
— | Element ID |
aria-label |
string |
— | Accessible label |
class |
string |
— | Additional CSS classes |
SpToast renders role="status", aria-live="polite", and
aria-atomic="true" by default. Pass content to a named icon slot to wrap it
in getToastIconClasses styling; the wrapper is only rendered when the slot is
used.
<SpToast variant="success">Changes saved.</SpToast>
<SpToast variant="danger">
<Fragment slot="icon"><svg aria-hidden="true">...</svg></Fragment>
Something went wrong.
</SpToast>| Prop | Type | Default | Description |
|---|---|---|---|
placement |
TooltipPlacement |
"top" |
Placement: "top" "bottom" "left" "right" |
visible |
boolean |
— | Applies visible state styling |
accent |
TooltipAccentEdge |
— | Decorative rail edge: "top" "right" "bottom" "left" |
accentColor |
TooltipAccentColor |
"brand" |
Rail color, used when accent is set |
as |
"div" | "span" |
"div" |
Rendered element |
id |
string |
— | Element ID |
role |
string |
"tooltip" |
ARIA role |
class |
string |
— | Additional CSS classes |
<SpTooltip placement="bottom" visible>Save your changes</SpTooltip>SpTabs renders a deterministic tab list from tabs; pair each definition with
an SpTabPanel using the same id. Selection is controlled by activeId.
<SpTabs tabs={[{ id: 'overview', label: 'Overview' }, { id: 'activity', label: 'Activity' }]} activeId="overview">
<SpTabPanel id="overview" active>Overview content</SpTabPanel>
<SpTabPanel id="activity">Activity content</SpTabPanel>
</SpTabs>SpTabPanel renders a role="tabpanel" associated with the matching tab
button. Pass active to expose it and disabled to keep it hidden.
SpAccordion applies the upstream group recipe. Use flush for edge-to-edge
composition and place SpAccordionItem children in its default slot.
SpAccordionItem uses native <details>/<summary> markup, with header and
optional icon slots plus the default panel slot. open sets the initial
expanded state without client JavaScript.
Pass an items array of { label, href?, current? }. Current items receive
aria-current="page"; setting separator uses the custom-separator recipe.
SpListGroup forwards flush, horizontal, accent, and accentColor and
accepts div, ul, ol, or nav through as.
SpListGroupItem supports div, li, a, and button roots and forwards
interactive, active, selected, disabled, hover, and focus states. Named
heading and text slots receive the matching sub-recipe classes.
SpLogoCloud forwards size (sm | md | lg), fill (subtle | card |
none), and muted to getLogoCloudClasses, and accepts div, ul, or
section through as. With muted, marks are grayscale until hover or focus.
SpLogoCloudItem is one tile, styled by getLogoCloudItemClasses. It renders a
div, li, or a root; href is only kept on a. The Lit sp-logo-cloud
styles its direct children at runtime, so SSR markup uses this part instead.
<SpLogoCloud as="ul" size="sm" muted aria-label="Partners">
<SpLogoCloudItem as="li"><img src="/partner-logo.svg" alt="Partner logo" /></SpLogoCloudItem>
</SpLogoCloud>SpOffcanvas renders a controlled backdrop and dialog panel. Use open and
placement, with named header/footer slots and the default body slot.
SpCarousel provides the root, viewport, optional previous/next controls,
indicator wrapper, and caption. Compose slides and indicators with the
re-exported getCarouselSlideClasses and getCarouselIndicatorClasses.
SpTable renders a responsive wrapper and semantic <table>, forwarding
size, striped, hoverable, and bordered. Use getTableRowClasses for
contextual or selected rows. A slotted <caption> and <tfoot> take their
styles from the upstream table recipe.
SpPagination renders page ranges from page, total, and siblings. Set
hrefTemplate with a {page} placeholder for crawlable links.
SpStepper renders an ordered list from steps and derives done, active,
and pending states from the zero-based current index. A step-level state
overrides the derived state. tone ("brand" | "accent") colours the active
and done steps; without it the step classes are unchanged.
SpHeading applies the typography.heading preset through level
("h1"–"h6", default "h2"). The element follows level unless as is set,
so the document outline and the visual scale can differ.
<SpHeading level="h4" as="h2">Section title styled as h4</SpHeading>SpDisplay applies the marketing-scale typography.display preset through
level (1–6, default 1). It renders h{level} unless as is set.
SpLead renders an introductory <p> (or div/span through as) with the
typography.lead preset.
SpProse wraps long-form or CMS-authored HTML in the upstream prose recipe. Use
as for div, article, section, main, or aside. Set accentMarkers to
colour list markers with the accent role instead of the muted one. Set size
(md | lg | xl) to set the body text size; omitted inherits the surrounding
size. Edit marks (<del>, <ins>, <s>), description lists, <figcaption>,
<caption>, underlines, <meter>, and syntax highlighting inside <pre> come
from the upstream base and prose styles with no extra props.
SpLink renders an <a> on getLinkClasses. tone="onInverse" switches to
the on-inverse link role for dark surfaces; decoration (thin | base |
thick) and underlineOffset (sm | md | lg) override the default
underline. current sets aria-current="page".
<SpLink href="/docs">Read the docs</SpLink>
<SpLink href="/" tone="onInverse" decoration="thick" current>Home</SpLink>SpDivider renders an <hr> on getDividerClasses, in the surface.divider
role at the component border width. Use it between content sections; inside a
dropdown menu or footer use SpDropdownDivider or SpFooterDivider.
<SpDivider />SpSurface paints a --sp-surface-* background role from getSurfaceClasses:
page, card, subtle, overlay, input, inverse, or hero. hover,
selected, and active add the matching interaction states. Use as for div
(default), section, article, aside, header, footer, or li. Spacing,
borders, and elevation come from the utility helpers (see
Recipe Helpers).
---
import { SpSurface, getSpacingClasses } from '@phcdevworks/spectre-ui-astro'
---
<SpSurface as="section" surface="subtle" class={getSpacingClasses({ p: 24 })}>
Panel content
</SpSurface>SpSwitch renders <input type="checkbox" role="switch"> and forwards size,
checked, disabled, and focused. Native :checked and :disabled drive
state without script.
<SpLabel htmlFor="alerts">Email alerts</SpLabel>
<SpSwitch id="alerts" name="alerts" checked />SpChoiceCard renders a whole-card <label> around a native radio (default) or
checkbox (type="checkbox"), so the entire card is the hit target. Pass name,
value, checked, and disabled for the input; selected, hovered, and
focused force the matching recipe states. Card content goes in the default
slot.
<SpChoiceCard name="shipping" value="express" checked>
<strong>Express</strong> — arrives tomorrow
</SpChoiceCard>SpFileInput renders <input type="file"> and forwards size, state,
fullWidth, disabled, and focused. state="invalid" also sets
aria-invalid="true". Native accept, multiple, and capture pass through.
SpRange renders <input type="range"> with min (default 0), max
(default 100), step, and value. When value is set it also mirrors the
fill percentage into the upstream-owned --sp-component-range-value property so
WebKit/Blink paint the filled track; update that property from client script if
the slider changes after render.
SpInputGroup renders a role="group" wrapper that fuses inputs, selects, file
inputs, buttons, and addons into one control. Pair it with SpInputGroupAddon
for static text or icons.
<SpInputGroup aria-label="Price">
<SpInputGroupAddon>$</SpInputGroupAddon>
<SpInput name="price" />
<SpButton>Apply</SpButton>
</SpInputGroup>SpInputGroupAddon renders a span (or div/label through as) with the
input-group addon recipe.
SpExternalAuthButton renders the neutral, mode-aware third-party sign-in
button. Provider logos go in the icon slot; the recipe encodes no brand color.
It supports button and a roots and forwards fullWidth, disabled,
loading, hovered, focused, and active.
<SpExternalAuthButton as="a" href="/auth/provider" fullWidth>
<svg slot="icon" aria-hidden="true"><!-- provider logo --></svg>
Continue with Provider
</SpExternalAuthButton>SpPopover renders a titled, interactive overlay (role="dialog" by default).
Place it inside a position: relative trigger wrapper and control visibility
with open; placement accepts top, bottom, left, or right. Use
title or the header slot for the header, the default slot for the body, and
arrow={false} to omit the pointer. With an id, the header is wired to
aria-labelledby.
SpProgress renders a role="progressbar" track and bar. Pass value (with
optional min/max) for a determinate bar; omit it or set indeterminate for
the animated sweep. label renders the upstream label above the track and, with
an id, labels the bar through aria-labelledby. variant and size forward
to the bar and track recipes.
<SpProgress id="upload" label="Uploading" value={40} variant="success" />SpDay renders a single calendar day cell (a button by default, or a,
span, div, or td) and forwards selected, today, outsideMonth,
disabled, hovered, and focused. Buttons expose aria-pressed; today
adds aria-current="date".
SpDatepicker renders a deterministic month grid for year and month
(1–12). ISO YYYY-MM-DD strings drive selected, today, min, max,
and disabledDates, so SSR output never depends on the server clock. Use
weekStartsOn (0 or 1), showOutsideDays, and locale to adjust the grid,
and the previous/next slots for navigation controls. Each day renders
through SpDay with a data-date attribute and a full-date aria-label.
<SpDatepicker id="due" year={2026} month={9} selected="2026-09-15" today="2026-09-27">
<SpButton slot="previous" variant="ghost" size="sm" aria-label="Previous month">‹</SpButton>
<SpButton slot="next" variant="ghost" size="sm" aria-label="Next month">›</SpButton>
</SpDatepicker>These part components apply the sub-part recipes that
@phcdevworks/spectre-components applies to marked children at runtime. In
Astro the parts are explicit components, so SSR output carries the classes with
no client script.
SpCardBleed runs media or a full-width band flush through an SpCard's
padding. edges takes one edge, an array of edges, or "all"; padded must
match the parent card's padding step.
<SpCard padded="lg">
<SpCardBleed edges={["top", "left", "right"]} padded="lg" as="figure">
<img src="/cover.jpg" alt="" />
</SpCardBleed>
<p>Card body</p>
</SpCard>SpDropdownMenu renders the menu panel inside SpDropdown and forwards open,
placement, mega, viewport, accent, and accentColor.
<SpDropdown>
<SpButton variant="secondary">Options</SpButton>
<SpDropdownMenu open aria-label="Options">
<SpDropdownHeader>Account</SpDropdownHeader>
<SpDropdownItem href="/settings">Settings</SpDropdownItem>
<SpDropdownDivider />
<SpDropdownItem disabled>Archive</SpDropdownItem>
</SpDropdownMenu>
</SpDropdown>SpDropdownItem renders an a when href is set and a button otherwise
(override with as). It forwards active, selected, disabled, hovered,
and focused. Disabled links drop their href; selected links get
aria-current="page".
SpDropdownHeader labels a group of dropdown items (div by default).
SpDropdownDivider renders an <hr> between dropdown item groups.
SpFooterHeading renders a footer column heading (h2 by default; use as to
match the document outline).
SpFooterText renders footer body text in a p. Set muted for the muted
recipe (for example, copyright lines).
SpFooterLinks renders the footer link list (ul by default). Place
SpFooterLink items inside li elements.
<SpFooter>
<SpFooterHeading>Company</SpFooterHeading>
<SpFooterLinks aria-label="Company">
<li><SpFooterLink href="/about">About</SpFooterLink></li>
</SpFooterLinks>
<SpFooterDivider />
<SpFooterText muted>© PHCDevworks</SpFooterText>
</SpFooter>SpFooterDivider renders an <hr> between footer rows.
SpNavLinks groups the links inside SpNav (div or ul).
SpSidebarGroup renders a collapsible native <details> group with a styled
<summary>. Pass label or use the summary slot, and open for the initial
state. The summary ends in a chevron (getSidebarGroupIconClasses) that turns
when the group opens; replace it through the icon slot. Children in the
content slot are wrapped in getSidebarGroupContentClasses for the group's
bottom spacing; the default slot renders unwrapped. hovered and focused
force the summary's hover and focus look.
<SpSidebar>
<SpSidebarHeader>Docs</SpSidebarHeader>
<SpSidebarGroup label="Guides" open>
<div slot="content">
<SpSidebarLink href="/guides/intro">Intro</SpSidebarLink>
</div>
</SpSidebarGroup>
</SpSidebar>SpSidebarHeader renders a sidebar section header (div by default).
SpTableRow renders a <tr> for SpTable and forwards the contextual
variant (neutral, info, success, warning, danger) and selected,
which also sets aria-selected.
SpCarouselSlide renders a carousel slide with role="group" and
aria-roledescription="slide". Mark the visible slide active and label each
slide (for example, "1 of 3").
SpCarouselIndicator renders an indicator button for SpCarousel's
indicators slot. aria-label is required; active adds aria-current="true"
and target sets aria-controls.
<SpCarousel aria-label="Highlights">
<SpCarouselSlide id="slide-1" active aria-label="1 of 2">First</SpCarouselSlide>
<SpCarouselSlide id="slide-2" aria-label="2 of 2">Second</SpCarouselSlide>
<Fragment slot="indicators">
<SpCarouselIndicator active target="slide-1" aria-label="Go to slide 1" />
<SpCarouselIndicator target="slide-2" aria-label="Go to slide 2" />
</Fragment>
</SpCarousel>Most components accept an as prop to change the rendered HTML element without
changing component behavior or styling.
<!-- SpButton: renders <button> by default -->
<SpButton variant="primary">Submit</SpButton>
<!-- SpButton: renders <a> with all button styling -->
<SpButton variant="primary" as="a" href="/get-started">Get started</SpButton>
<!-- SpCard: semantic article markup -->
<SpCard variant="elevated" as="article">
<h2>Article title</h2>
</SpCard>
<!-- SpBadge: inline time element -->
<SpBadge variant="primary" as="time" datetime="2026-01-01">Jan 2026</SpBadge>Disabled navigation: When disabled is set on an anchor (as="a"), the
href is suppressed so the element is not keyboard-navigable.
aria-disabled="true" is always set alongside disabled.
Button type: When as="button" (the default for SpButton), type
defaults to "button" to prevent accidental form submission. Pass
type="submit" explicitly when needed.
Non-native interactive elements: Setting interactive={true} on a div or
span adds role="button" and tabindex="0" automatically. Use this only when
a native button or a is genuinely impractical.
@phcdevworks/spectre-ui-astro works in all Astro output modes: "static",
"server", and "hybrid".
No client-side JavaScript. Every component renders entirely at build time or
in the server step. No hydration directives are needed or used. Interactive
styles (hover, focus, active) are driven by CSS from @phcdevworks/spectre-ui.
Deterministic markup. All IDs, class names, and attributes are computed from explicit props. Every render of the same props produces identical HTML — safe for SSR streaming and static generation.
SpInput explicit id requirement. When any of label, helperText, or
errorMessage are passed, an explicit id prop is required. Without it, the
component throws during render to prevent nondeterministic
for/aria-describedby wiring in server-rendered and statically generated
output.
<!-- id is required whenever label, helperText, or errorMessage is set -->
<SpInput id="email" label="Email" name="email" />Omitting id when label is set throws at render time with a clear error
message. This applies in both SSR and static builds.
CSS in SSR. Import the Spectre UI stylesheet in your Astro layout once. Astro handles CSS bundling and injection in both SSR and static builds — no runtime style injection occurs from this package.
The package re-exports class recipe functions from @phcdevworks/spectre-ui.
Use these when you need Spectre-aligned class names outside of the Astro
components — in layout markup, in headless patterns, or when mapping over
dynamic data.
---
// Using getButtonClasses to style plain anchor tags in a nav
import { getButtonClasses } from '@phcdevworks/spectre-ui-astro'
const navClass = getButtonClasses({ variant: 'ghost', size: 'sm' })
const links = [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/blog', label: 'Blog' },
]
---
<nav>
{links.map(link => (
<a class={navClass} href={link.href}>{link.label}</a>
))}
</nav>| Helper | For |
|---|---|
getAccordion*Classes |
Accordion root and item structure |
getAlertClasses |
Alert class generation |
getAlertIconClasses |
Alert icon wrapper |
getAlertDismissClasses |
Alert dismiss control |
getAvatarClasses |
Avatar class generation |
getButtonClasses |
Button class generation |
getBreadcrumb*Classes |
Breadcrumb root and item structure |
getCardClasses |
Card class generation |
getCardBleedClasses |
Full-bleed child composition inside a padded card |
getCarousel*Classes |
Carousel root, slides, controls, and indicators |
getChoiceCardClasses |
Whole-card radio/checkbox option |
getBadgeClasses |
Badge class generation |
getContainerClasses |
Container class generation |
getDatepicker*Classes |
Datepicker panel, header, grid, and weekdays |
getDayClasses |
Calendar day cell |
getDisplayClasses |
Display (hero) typography |
getDividerClasses |
Horizontal rule / section separator |
getDropdownClasses |
Dropdown root classes |
getDropdownMenuClasses |
Dropdown menu container |
getDropdownItemClasses |
Individual dropdown item |
getDropdownHeaderClasses |
Dropdown menu section header |
getDropdownDividerClasses |
Dropdown menu divider |
getExternalAuthButton*Classes |
Third-party sign-in button and icon slot |
getFileInputClasses |
Native file input |
getFooterClasses |
Footer class generation |
getHeadingClasses |
Heading typography presets |
getIconBoxClasses |
Icon box class generation |
getInputClasses |
Input class generation |
getInputGroup*Classes |
Input group wrapper and addon |
getLeadClasses |
Lead paragraph typography |
getLinkClasses |
Inline link tone and underline |
getListGroup*Classes |
List-group root and item structure |
getLogoCloud*Classes |
Logo cloud root and tile |
getModalClasses |
Modal root classes |
getModalOverlayClasses |
Modal overlay/backdrop classes |
getNavClasses |
Nav root classes |
getNavLinksClasses |
Nav links container |
getNavLinkClasses |
Individual nav link |
getOffcanvas*Classes |
Offcanvas panel and backdrop structure |
getPagination*Classes |
Pagination root, items, and ellipsis |
getPopover*Classes |
Popover root, header, body, and arrow |
getPricingCardClasses |
Pricing card root classes |
getPricingCardBadgeClasses |
Pricing card badge wrapper |
getPricingCardPriceContainerClasses |
Pricing card price container |
getPricingCardPriceClasses |
Pricing card price element |
getPricingCardDescriptionClasses |
Pricing card description |
getProgress*Classes |
Progress track, bar, and label |
getProseClasses |
Long-form prose content |
getQuote*Classes |
Quote root, text, and attribution parts |
getRangeClasses |
Native range slider |
getRatingClasses |
Rating root classes |
getRatingStarsClasses |
Rating star container |
getRatingStarClasses |
Individual star element |
getRatingTextClasses |
Rating text element |
getSectionClasses |
Section class generation |
getSectionHead*Classes |
Section head root, title, and lead parts |
getSidebarClasses |
Sidebar root classes |
getSidebarGroupClasses |
Collapsible sidebar group |
getSidebarGroupSummaryClasses |
Sidebar group summary |
getSidebarGroupIconClasses |
Sidebar group disclosure chevron |
getSidebarGroupContentClasses |
Links under a sidebar group summary |
getSidebarLinkClasses |
Individual sidebar link |
getSidebarHeaderClasses |
Sidebar section header |
getSidebarBackdropClasses |
Sidebar off-canvas backdrop |
getSidebarToggleClasses |
Sidebar toggle button classes |
getSkeletonClasses |
Skeleton loading placeholder |
getStackClasses |
Stack class generation |
getStat*Classes |
Stat root, figure, unit, and label |
getStepper*Classes |
Stepper root, steps, indicators, and labels |
getSurfaceClasses |
Surface background roles and interaction states |
getSwitchClasses |
Toggle switch |
getTable*Classes |
Table wrapper, table, and row classes |
getTabs*Classes |
Tabs root, list, items, and panels |
getTestimonialClasses |
Testimonial root classes |
getTestimonialQuoteClasses |
Quote wrapper |
getTestimonialAuthorClasses |
Author section wrapper |
getTestimonialAuthorInfoClasses |
Author info wrapper |
getTestimonialAuthorNameClasses |
Author name element |
getTestimonialAuthorTitleClasses |
Author title element |
getTextClasses |
Typography class generation |
getToastClasses |
Toast root classes |
getToastIconClasses |
Toast icon wrapper |
getTooltipClasses |
Tooltip class generation |
Every token-backed utility class in @phcdevworks/spectre-ui has a recipe, so
layout, spacing, and effects never need hand-written class names or local CSS.
These families are helper-only: there is no Astro component for them. Call the
helper in a class attribute, on its own or next to a component's class.
---
import { SpCard, getLayoutClasses, getSpacingClasses } from '@phcdevworks/spectre-ui-astro'
---
<div class={getLayoutClasses({ display: 'flex', md: { display: 'grid' } })}>
<SpCard class={getSpacingClasses({ mb: 16, lg: { mb: 32 } })}>…</SpCard>
</div>| Helper | For |
|---|---|
getSpacingClasses |
Padding, margin (including auto), gap, and basis, with sm–2xl |
getLayoutClasses |
Display, flex, alignment, position, sizing, overflow, and more |
getColorClasses |
Semantic text, background, and border colors |
getPaletteClasses |
Palette hue text, background, and border colors |
getBorderClasses |
Thick sides, border style, width, and radius |
getShadowClasses |
Shadow and inset shadow steps |
getOpacityClasses |
Opacity roles |
getZIndexClasses |
Z-index roles |
getIconSizeClasses |
Icon size steps |
getAspectRatioClasses |
Aspect ratios |
getElevationClasses |
flat, raised, overlay, and modal (surface + shadow + z-index) |
getMotionClasses |
Animation, transition, duration, and easing |
getChartColorClasses |
Chart fill, stroke, background, text, and border roles |
spectreThemeModes and spectreDensityModes (typed SpectreThemeMode and
SpectreDensityMode) list every data-spectre-theme and data-spectre-density
value, for typing a theme or density switch.
Recipe option and variant types are also re-exported: AlertRecipeOptions,
AlertVariant, AlertSize, AvatarRecipeOptions, AvatarShape, AvatarSize,
BadgeRecipeOptions, BadgeVariant, BadgeSize, ButtonRecipeOptions,
ButtonVariant, ButtonSize, CardRecipeOptions, CardVariant,
ContainerRecipeOptions, ContainerMaxWidth, DropdownRecipeOptions,
DropdownMenuRecipeOptions, DropdownItemRecipeOptions, DropdownPlacement,
FooterRecipeOptions, IconBoxRecipeOptions, IconBoxVariant, IconBoxSize,
InputRecipeOptions, InputState, InputSize, ModalRecipeOptions,
ModalOverlayRecipeOptions, NavRecipeOptions, NavAlign,
NavLinkRecipeOptions, PricingCardRecipeOptions, RatingRecipeOptions,
SectionRecipeOptions, SidebarRecipeOptions, SidebarLinkRecipeOptions,
SidebarLinkLevel, StackRecipeOptions, StackDirection, StackBasis,
StackAlign, StackGap, TestimonialRecipeOptions, TextRecipeOptions,
TextSize, TextVariant, TextFamily, TextTransform, ToastRecipeOptions,
ToastIconRecipeOptions, ToastVariant, TooltipRecipeOptions,
TooltipPlacement, GridRecipeOptions, GridAlign, GridColumns, GridGap,
GridSpan, GridSpanOptions.
The corresponding accordion, breadcrumb, carousel, choice-card, datepicker, day,
display, external-auth-button, file-input, heading, input-group, list-group,
logo-cloud, offcanvas, pagination, popover, progress, prose, quote, range,
section-head, skeleton, stat, stepper, switch, table, and tabs recipe option,
variant, size, placement, orientation, state, and accent types are also
re-exported, as are the container padding, section spacing/gap, card padding,
grid column-start, footer appearance/surface, stepper step tone, responsive grid
columns, and text weight, leading, font-size, tracking, decoration, and
underline-offset types, plus StatUnitTone, ProseSize, IconBoxIconSize, and
SidebarInteractiveRecipeOptions. Link, surface, and every utility helper's
option and scale types are re-exported too. Every public get*Classes helper
from @phcdevworks/spectre-ui is re-exported; the upstream parity test fails if
one is missed.
import {
SpAccordion,
SpAccordionItem,
SpAlert,
SpAvatar,
SpBadge,
SpButton,
SpBreadcrumb,
SpCard,
SpCarousel,
SpCheckbox,
SpContainer,
SpDropdown,
SpFieldset,
SpFooter,
SpGrid,
SpIcon,
SpIconBox,
SpInput,
SpLabel,
SpListGroup,
SpListGroupItem,
SpLogoCloud,
SpLogoCloudItem,
SpModal,
SpNav,
SpOffcanvas,
SpPagination,
SpPricingCard,
SpRadio,
SpRating,
SpSection,
SpSelect,
SpSidebar,
SpSidebarToggle,
SpSkeleton,
SpSpinner,
SpStack,
SpStepper,
SpTabPanel,
SpTable,
SpTabs,
SpTag,
SpTestimonial,
SpText,
SpTextarea,
SpToast,
SpTooltip
} from '@phcdevworks/spectre-ui-astro'
import {
getButtonClasses,
getBadgeClasses,
getCardClasses
// …all recipe helpers and types
} from '@phcdevworks/spectre-ui-astro'import SpAccordion from '@phcdevworks/spectre-ui-astro/components/SpAccordion.astro'
import SpAccordionItem from '@phcdevworks/spectre-ui-astro/components/SpAccordionItem.astro'
import SpAlert from '@phcdevworks/spectre-ui-astro/components/SpAlert.astro'
import SpAvatar from '@phcdevworks/spectre-ui-astro/components/SpAvatar.astro'
import SpBadge from '@phcdevworks/spectre-ui-astro/components/SpBadge.astro'
import SpButton from '@phcdevworks/spectre-ui-astro/components/SpButton.astro'
import SpBreadcrumb from '@phcdevworks/spectre-ui-astro/components/SpBreadcrumb.astro'
import SpCard from '@phcdevworks/spectre-ui-astro/components/SpCard.astro'
import SpCardBleed from '@phcdevworks/spectre-ui-astro/components/SpCardBleed.astro'
import SpCarousel from '@phcdevworks/spectre-ui-astro/components/SpCarousel.astro'
import SpCarouselIndicator from '@phcdevworks/spectre-ui-astro/components/SpCarouselIndicator.astro'
import SpCarouselSlide from '@phcdevworks/spectre-ui-astro/components/SpCarouselSlide.astro'
import SpCheckbox from '@phcdevworks/spectre-ui-astro/components/SpCheckbox.astro'
import SpChoiceCard from '@phcdevworks/spectre-ui-astro/components/SpChoiceCard.astro'
import SpContainer from '@phcdevworks/spectre-ui-astro/components/SpContainer.astro'
import SpDatepicker from '@phcdevworks/spectre-ui-astro/components/SpDatepicker.astro'
import SpDay from '@phcdevworks/spectre-ui-astro/components/SpDay.astro'
import SpDisplay from '@phcdevworks/spectre-ui-astro/components/SpDisplay.astro'
import SpDivider from '@phcdevworks/spectre-ui-astro/components/SpDivider.astro'
import SpDropdown from '@phcdevworks/spectre-ui-astro/components/SpDropdown.astro'
import SpDropdownDivider from '@phcdevworks/spectre-ui-astro/components/SpDropdownDivider.astro'
import SpDropdownHeader from '@phcdevworks/spectre-ui-astro/components/SpDropdownHeader.astro'
import SpDropdownItem from '@phcdevworks/spectre-ui-astro/components/SpDropdownItem.astro'
import SpDropdownMenu from '@phcdevworks/spectre-ui-astro/components/SpDropdownMenu.astro'
import SpExternalAuthButton from '@phcdevworks/spectre-ui-astro/components/SpExternalAuthButton.astro'
import SpFieldset from '@phcdevworks/spectre-ui-astro/components/SpFieldset.astro'
import SpFileInput from '@phcdevworks/spectre-ui-astro/components/SpFileInput.astro'
import SpFooter from '@phcdevworks/spectre-ui-astro/components/SpFooter.astro'
import SpFooterChip from '@phcdevworks/spectre-ui-astro/components/SpFooterChip.astro'
import SpFooterDivider from '@phcdevworks/spectre-ui-astro/components/SpFooterDivider.astro'
import SpFooterHeading from '@phcdevworks/spectre-ui-astro/components/SpFooterHeading.astro'
import SpFooterLink from '@phcdevworks/spectre-ui-astro/components/SpFooterLink.astro'
import SpFooterLinks from '@phcdevworks/spectre-ui-astro/components/SpFooterLinks.astro'
import SpFooterText from '@phcdevworks/spectre-ui-astro/components/SpFooterText.astro'
import SpGrid from '@phcdevworks/spectre-ui-astro/components/SpGrid.astro'
import SpHeading from '@phcdevworks/spectre-ui-astro/components/SpHeading.astro'
import SpIcon from '@phcdevworks/spectre-ui-astro/components/SpIcon.astro'
import SpIconBox from '@phcdevworks/spectre-ui-astro/components/SpIconBox.astro'
import SpInput from '@phcdevworks/spectre-ui-astro/components/SpInput.astro'
import SpInputGroup from '@phcdevworks/spectre-ui-astro/components/SpInputGroup.astro'
import SpInputGroupAddon from '@phcdevworks/spectre-ui-astro/components/SpInputGroupAddon.astro'
import SpLabel from '@phcdevworks/spectre-ui-astro/components/SpLabel.astro'
import SpLead from '@phcdevworks/spectre-ui-astro/components/SpLead.astro'
import SpLink from '@phcdevworks/spectre-ui-astro/components/SpLink.astro'
import SpListGroup from '@phcdevworks/spectre-ui-astro/components/SpListGroup.astro'
import SpListGroupItem from '@phcdevworks/spectre-ui-astro/components/SpListGroupItem.astro'
import SpLogoCloud from '@phcdevworks/spectre-ui-astro/components/SpLogoCloud.astro'
import SpLogoCloudItem from '@phcdevworks/spectre-ui-astro/components/SpLogoCloudItem.astro'
import SpModal from '@phcdevworks/spectre-ui-astro/components/SpModal.astro'
import SpNav from '@phcdevworks/spectre-ui-astro/components/SpNav.astro'
import SpNavItem from '@phcdevworks/spectre-ui-astro/components/SpNavItem.astro'
import SpNavLinks from '@phcdevworks/spectre-ui-astro/components/SpNavLinks.astro'
import SpOffcanvas from '@phcdevworks/spectre-ui-astro/components/SpOffcanvas.astro'
import SpPagination from '@phcdevworks/spectre-ui-astro/components/SpPagination.astro'
import SpPopover from '@phcdevworks/spectre-ui-astro/components/SpPopover.astro'
import SpPricingCard from '@phcdevworks/spectre-ui-astro/components/SpPricingCard.astro'
import SpProgress from '@phcdevworks/spectre-ui-astro/components/SpProgress.astro'
import SpProse from '@phcdevworks/spectre-ui-astro/components/SpProse.astro'
import SpQuote from '@phcdevworks/spectre-ui-astro/components/SpQuote.astro'
import SpRadio from '@phcdevworks/spectre-ui-astro/components/SpRadio.astro'
import SpRange from '@phcdevworks/spectre-ui-astro/components/SpRange.astro'
import SpRating from '@phcdevworks/spectre-ui-astro/components/SpRating.astro'
import SpSection from '@phcdevworks/spectre-ui-astro/components/SpSection.astro'
import SpSectionHead from '@phcdevworks/spectre-ui-astro/components/SpSectionHead.astro'
import SpSelect from '@phcdevworks/spectre-ui-astro/components/SpSelect.astro'
import SpSidebar from '@phcdevworks/spectre-ui-astro/components/SpSidebar.astro'
import SpSidebarGroup from '@phcdevworks/spectre-ui-astro/components/SpSidebarGroup.astro'
import SpSidebarHeader from '@phcdevworks/spectre-ui-astro/components/SpSidebarHeader.astro'
import SpSidebarLink from '@phcdevworks/spectre-ui-astro/components/SpSidebarLink.astro'
import SpSidebarToggle from '@phcdevworks/spectre-ui-astro/components/SpSidebarToggle.astro'
import SpSkeleton from '@phcdevworks/spectre-ui-astro/components/SpSkeleton.astro'
import SpSpinner from '@phcdevworks/spectre-ui-astro/components/SpSpinner.astro'
import SpStack from '@phcdevworks/spectre-ui-astro/components/SpStack.astro'
import SpStat from '@phcdevworks/spectre-ui-astro/components/SpStat.astro'
import SpStepper from '@phcdevworks/spectre-ui-astro/components/SpStepper.astro'
import SpSurface from '@phcdevworks/spectre-ui-astro/components/SpSurface.astro'
import SpSwitch from '@phcdevworks/spectre-ui-astro/components/SpSwitch.astro'
import SpTableRow from '@phcdevworks/spectre-ui-astro/components/SpTableRow.astro'
import SpTabPanel from '@phcdevworks/spectre-ui-astro/components/SpTabPanel.astro'
import SpTable from '@phcdevworks/spectre-ui-astro/components/SpTable.astro'
import SpTabs from '@phcdevworks/spectre-ui-astro/components/SpTabs.astro'
import SpTag from '@phcdevworks/spectre-ui-astro/components/SpTag.astro'
import SpTestimonial from '@phcdevworks/spectre-ui-astro/components/SpTestimonial.astro'
import SpText from '@phcdevworks/spectre-ui-astro/components/SpText.astro'
import SpTextarea from '@phcdevworks/spectre-ui-astro/components/SpTextarea.astro'
import SpToast from '@phcdevworks/spectre-ui-astro/components/SpToast.astro'
import SpTooltip from '@phcdevworks/spectre-ui-astro/components/SpTooltip.astro'The adapter does not export a CSS helper or path. Import the stylesheet directly
from @phcdevworks/spectre-ui/index.css.
Each component family is classified by its support status in this adapter.
| Family | Status | Notes |
|---|---|---|
| accordion | stable | Native details semantics, slot, state, and SSR coverage |
| alert | stable | Full prop, slot, ARIA, and SSR coverage |
| avatar | stable | Full prop, slot, ARIA, and SSR coverage |
| badge | stable | Full prop, slot, ARIA, and SSR coverage |
| breadcrumb | stable | Item, current-page, separator, and SSR coverage |
| button | stable | Full prop, slot, ARIA, and SSR coverage |
| card | stable | Full prop, slot, ARIA, and SSR coverage |
| carousel | stable | Shell, controls, slot, ARIA, and SSR coverage |
| checkbox | stable | Full prop, ARIA, and SSR coverage |
| choice-card | stable | Label-wrapped radio/checkbox, forced state, slot, and SSR coverage |
| container | stable | Full prop, slot, ARIA, and SSR coverage |
| datepicker | stable | Deterministic month grid, locale, bounds, ARIA, and SSR coverage |
| day | stable | Selected/today/outside/disabled state, ARIA, and SSR coverage |
| display | stable | Level, element override, slot, and SSR coverage |
| divider | stable | <hr>, pass-through attributes, and SSR coverage |
| dropdown | stable | Full prop, slot, and SSR coverage |
| external-auth-button | stable | Icon slot, loading/disabled, link mode, ARIA, and SSR coverage |
| fieldset | stable | Full prop, slot, ARIA, and SSR coverage |
| file-input | stable | Size/state, native file attributes, ARIA, and SSR coverage |
| footer | stable | Full prop, slot, ARIA, and SSR coverage |
| grid | stable | Full prop, slot, and SSR coverage |
| heading | stable | Level, element override, slot, and SSR coverage |
| icon-box | stable | Full prop, slot, ARIA, and SSR coverage |
| input | stable | Full prop, ARIA, SSR, and explicit id invariant coverage |
| input-group | stable | Group role, addon, disabled, and SSR coverage |
| label | stable | Full prop, slot, ARIA, and SSR coverage |
| lead | stable | Element override, slot, and SSR coverage |
| link | stable | Tone, underline overrides, aria-current, and SSR coverage |
| list-group | stable | Root/item, interaction-state, slot, and SSR coverage |
| logo-cloud | stable | Root/tile, size/fill/muted, and SSR coverage |
| modal | stable | Full prop, slot, ARIA, and SSR coverage |
| nav | stable | Full prop, slot, ARIA, and SSR coverage |
| offcanvas | stable | Controlled panel, backdrop, slot, ARIA, and SSR coverage |
| pagination | stable | Range, link-template, ARIA, and SSR coverage |
| popover | stable | Placement/open, header/body/arrow, ARIA, and SSR coverage |
| pricing-card | stable | Full prop, slot, ARIA, and SSR coverage |
| progress | stable | Determinate/indeterminate, label, ARIA, and SSR coverage |
| prose | stable | Element override, accent markers, slot, and SSR coverage |
| quote | stable | Full prop, attribution slots, and SSR coverage |
| radio | stable | Full prop, ARIA, and SSR coverage |
| range | stable | Bounds, WebKit fill mirroring, ARIA, and SSR coverage |
| rating | stable | Full prop, slot, ARIA, and SSR coverage |
| section | stable | Full prop, slot, ARIA, and SSR coverage |
| section-head | stable | Layout/align, title and lead slots, ARIA, and SSR coverage |
| select | stable | Full prop, slot, ARIA, and SSR coverage |
| sidebar | stable | Full prop, slot, ARIA, and SSR coverage; owns toggle interaction |
| skeleton | stable | Shape/animation, aria-hidden, and SSR coverage |
| spinner | stable | Full prop, ARIA, and SSR coverage |
| stack | stable | Full prop, slot, ARIA, and SSR coverage |
| stat | stable | Size/align/surface, unit and label slots, and SSR coverage |
| stepper | stable | Derived/explicit state, tone, ARIA, and SSR coverage |
| surface | stable | Every surface role, interaction states, as, and SSR coverage |
| switch | stable | Size/state, switch role, ARIA, and SSR coverage |
| table | stable | Responsive wrapper, semantic table, and SSR coverage |
| tabs | stable | Controlled selection, panel association, ARIA, and SSR coverage |
| tag | stable | Full prop, slot, ARIA, and SSR coverage |
| testimonial | stable | Full prop, slot, ARIA, and SSR coverage |
| text | stable | Full prop, slot, ARIA, and SSR coverage |
| textarea | stable | Full prop, ARIA, and SSR coverage |
| toast | stable | Full prop, slot, ARIA, and SSR coverage |
| tooltip | stable | Full prop, slot, ARIA, and SSR coverage |
| aspect-ratio | utility | getAspectRatioClasses helper; no component |
| border | utility | getBorderClasses helper; no component |
| chart-color | utility | getChartColorClasses helper; no component |
| color | utility | getColorClasses helper; no component |
| elevation | utility | getElevationClasses helper; no component |
| icon-size | utility | getIconSizeClasses helper; no component |
| layout | utility | getLayoutClasses helper; no component |
| motion | utility | getMotionClasses helper; no component |
| opacity | utility | getOpacityClasses helper; no component |
| palette | utility | getPaletteClasses helper; no component |
| shadow | utility | getShadowClasses helper; no component |
| spacing | utility | getSpacingClasses helper; no component |
| z-index | utility | getZIndexClasses helper; no component |
stable — the component family is fully wired to upstream recipes, covered by
SSR and unit tests, and declared in astro-adapter.contract.json. Breaking
changes require a semver major bump.
provisional — a family that is partially implemented or pending full test coverage. Not yet safe to depend on across minor releases.
not yet supported — families present in the upstream Spectre UI surface that this adapter has not yet bound.
utility — helper-only utility recipe families. The helper is re-exported unchanged and covered by the parity tests; by design there is no Astro component, because the classes compose onto any element.
The machine-readable classification lives in astro-adapter.contract.json under
componentFamilies.
| Package | Owns |
|---|---|
@phcdevworks/spectre-tokens |
Design values, semantic token meaning, and token contracts |
@phcdevworks/spectre-ui |
CSS, utilities, and type-safe class recipes |
@phcdevworks/spectre-components |
Framework-agnostic Lit web component behavior |
@phcdevworks/spectre-ui-astro |
Astro-native adapter delivery and framework ergonomics |
Tokens define meaning. UI defines the styling contract. Components define framework-agnostic custom element behavior. This package defines Astro delivery and consumes the upstream UI contract directly.
git clone https://github.com/phcdevworks/spectre-ui-astro.git
cd spectre-ui-astro
npm install| Command | Purpose |
|---|---|
npm run check |
Full pre-merge check: lint → build → typecheck → test |
npm run ci:verify |
Underlying verification sequence |
npm run build |
Build the distributable package |
npm run typecheck |
Type-check without emitting |
npm test |
Run the Vitest test suite |
npm run lint |
Run ESLint |
npm run dev |
Watch mode for development |
Run npm run check before opening any pull request. It is the single gate used
by CI.
This project requires Node.js ^22.13.0 || >=24.0.0.
src/components/— Astro component implementationssrc/recipes/— re-exported recipe bindings from@phcdevworks/spectre-uisrc/index.ts— package exportstests/— unit, SSR, and contract testsexamples/— demo Astro app for manual validation (not a contract authority; seeexamples/README.md)scripts/— packaging and contract validation scripts
npm run build fails with a missing recipe or type The upstream
@phcdevworks/spectre-ui peer dependency must be installed. Run npm install
from the repo root. If a recipe or type is missing from upstream, do not add it
locally — open an issue in @phcdevworks/spectre-ui first.
Tests fail after pulling upstream changes Run npm install to sync
installed peer versions, then re-run npm test. If exports.test.ts fails, the
public contract has drifted — check src/index.ts and package.json exports
against the test expectations.
SpInput renders without accessible label associations SpInput requires
an explicit id prop whenever label, helperText, or errorMessage is
passed. Without it, the component throws at render time to prevent broken
accessibility wiring.
Example app fails to build Run npm install from within examples/ (not
npm ci) because the example depends on the parent package through a local
file:.. link. Do not commit an example package-lock.json as a CI contract.
Run the full validation gate before any pull request:
npm run checkThis runs: lint → build → typecheck → tests. All steps must pass.
Claude Code (claude-sonnet-4-6) is the primary development agent for this
repository. Codex handles releases, including cutting tagged releases and GitHub
Releases, and production stabilization. Jules handles small automated fixes and
micro-updates. GitHub Copilot provides development support.
Codex, Copilot, and Jules have commit, push, and tag authority in this repository. Claude Code has no git access and hands validated work to Codex or the human maintainer. Publishing to npm remains the human maintainer's sole authority. See AGENTS.md for the full commit policy and release-authority grant.
Protected from automated change: SSR rendering invariants, the thin-adapter rule (no local CSS, no token redefinition, no recipe reimplementation), and the public export surface. See AGENTS.md for full agent governance and boundary rules.
PHCDevworks maintains this package as part of the Spectre suite.
When contributing:
- keep Astro components aligned with the upstream
@phcdevworks/spectre-uicontract - do not redefine tokens, CSS behavior, or recipe logic in this package
- keep the adapter SSR-friendly, type-safe, and framework-appropriate
- run
npm run checkbefore opening a pull request — it runs lint, build, typecheck, and tests in one step
See CONTRIBUTING.md for the full workflow.
MIT © PHCDevworks. See LICENSE.