@phcdevworks/spectre-components
v1.22.0
Published
@phcdevworks/spectre-components is the web-component layer of the Spectre system. It provides accessible, framework-independent interface components built on Spectre's shared design contracts.
Maintainers
Readme
@phcdevworks/spectre-components
@phcdevworks/spectre-components is the web-component layer of the Spectre
system. It provides accessible, framework-independent interface components built
on Spectre's shared design contracts.
Maintained by PHCDevworks. It draws on Spectre's token and styling contracts to ship drop-in UI primitives, so applications that need working components — rather than raw CSS or recipes to assemble themselves — can consume Spectre without a framework-specific adapter.
Repository Snapshot
| Field | Value |
| ---------------------- | ----------------------------------- |
| Project team | project-design |
| Repository role | Spectre L3a Lit web component layer |
| Package/artifact | @phcdevworks/spectre-components |
| Current version/status | 1.22.0 |
Standard Workflow
- 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.
Documentation Map
| 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 |
@phcdevworks/spectre-components is the Layer 3 Lit-based web component
package of the Spectre design system. It turns Spectre tokens
(@phcdevworks/spectre-tokens) and Spectre UI styling contracts
(@phcdevworks/spectre-ui) into reusable, accessible, framework-agnostic custom
elements — the canonical component implementation layer for Spectre, designed to
be consumed directly or wrapped by downstream adapter packages.
Contributing | Code of Conduct | Changelog | Roadmap | Security Policy
Why This Package Exists Alongside Spectre-UI
@phcdevworks/spectre-ui owns CSS: class recipes, Tailwind helpers, and the
styling contract that maps Spectre tokens to visual output. It ships CSS rules
and JavaScript class-name helpers — nothing more.
This package sits above that. It owns behavior: the Lit element classes that apply those CSS recipes, forward ARIA attributes to native elements, manage focus delegation, handle content projection, validate properties, and expose a stable TypeScript API surface for downstream adapters.
The separation keeps each layer focused:
| Layer | Package | Owns |
| ------ | ------------------------------------- | -------------------------------------- |
| L1 | @phcdevworks/spectre-tokens | Design values and semantic meaning |
| L2 | @phcdevworks/spectre-ui | CSS recipes and styling contracts |
| L3 | @phcdevworks/spectre-components | Lit web component behavior and API |
| L4 | Downstream adapters | Framework-specific delivery |
If you only need CSS class names, use @phcdevworks/spectre-ui directly. If you
need ready-to-use HTML elements with behavior, accessibility, and a typed API,
use this package.
Key Capabilities
- Lit-based custom elements on the Custom Elements standard
- Renders in light DOM so
@phcdevworks/spectre-uiglobal styles apply directly — no Shadow DOM piercing required - ARIA attributes (
aria-label,aria-labelledby,aria-describedby) are forwarded to the native element, not left on the host - Focus and blur delegate to the inner native element
- Property validation with safe fallbacks in
willUpdate() - Idempotent
defineSpectre*()helpers — safe to call multiple times - ESM + CJS dual build with TypeScript declaration files
- Tree-shakeable subpath exports per component
When To Use This Package
- You are building UI with the Spectre design system and want standards-based custom elements with baked-in behavior and accessibility.
- You want typed form controls (
sp-button,sp-input,sp-select, etc.) and display primitives (sp-badge,sp-card,sp-rating, etc.) that work in any framework or in plain HTML. - You are writing a framework adapter (React, Vue, Astro) and need a reliable, stable element layer to wrap.
When Not To Use This Package
- You only need CSS class names — use
@phcdevworks/spectre-uidirectly. - You are adding routing, shell logic, or app-startup orchestration — those are out of scope here.
- You need framework-specific component files (JSX, SFCs, Astro components) — those belong in a downstream adapter package.
Installation
npm install @phcdevworks/spectre-components @phcdevworks/spectre-ui @phcdevworks/spectre-tokensQuick Start
Plain HTML
Import the CSS layers and register all components from a script tag or entry module. These are standard custom elements — no build step required for consumption.
<!doctype html>
<html lang="en">
<head>
<!-- Spectre CSS layers must load before any markup is rendered -->
<link
rel="stylesheet"
href="/node_modules/@phcdevworks/spectre-tokens/index.css"
/>
<link
rel="stylesheet"
href="/node_modules/@phcdevworks/spectre-ui/index.css"
/>
</head>
<body>
<sp-label for="email">Email address</sp-label>
<sp-input
id="email"
name="email"
type="email"
placeholder="[email protected]"
></sp-input>
<sp-button variant="primary" type="submit">Send</sp-button>
<sp-button variant="ghost" type="button">Cancel</sp-button>
<script type="module">
import { defineSpectreComponents } from '/node_modules/@phcdevworks/spectre-components/dist/index.js'
defineSpectreComponents()
</script>
</body>
</html>JavaScript / TypeScript module
import '@phcdevworks/spectre-tokens/index.css'
import '@phcdevworks/spectre-ui/index.css'
// Register everything at once
import { defineSpectreComponents } from '@phcdevworks/spectre-components'
defineSpectreComponents()
// Or register only what you use
import { defineSpectreButton } from '@phcdevworks/spectre-components/button'
import { defineSpectreInput } from '@phcdevworks/spectre-components/input'
defineSpectreButton()
defineSpectreInput()Full form example
<sp-fieldset legend="Contact preferences">
<sp-label for="email">Email address</sp-label>
<sp-input id="email" name="email" type="email" required></sp-input>
<sp-label for="bio">Bio</sp-label>
<sp-textarea id="bio" name="bio" rows="4" maxlength="500"></sp-textarea>
<sp-label for="role">Role</sp-label>
<sp-select id="role" name="role">
<option value="admin">Admin</option>
<option value="user">User</option>
</sp-select>
<sp-checkbox name="terms" value="accepted" required>
I accept the <a href="/terms">terms of service</a>
</sp-checkbox>
<sp-radio name="plan" value="monthly">Monthly billing</sp-radio>
<sp-radio name="plan" value="annual">Annual billing</sp-radio>
<sp-button variant="primary" type="submit">Save</sp-button>
<sp-button variant="ghost" type="reset">Reset</sp-button>
</sp-fieldset>Framework integration note
These are standard HTML custom elements. They work in every major framework that supports the Custom Elements standard:
React 19+ — supports custom element properties and events natively:
// React 19: properties and events work directly
<sp-input name="email" type="email" onInput={(e) => setValue(e.target.value)} />React 18 and below — set attributes via ref for properties, listen for
native events on the element:
const inputRef = useRef(null)
useEffect(() => {
if (inputRef.current) inputRef.current.invalid = true
}, [])
return <sp-input ref={inputRef} name="email" />Vue 3 — supports custom elements out of the box with v-bind and v-on
directive compatibility. Mark the sp-* prefix in compilerOptions as a custom
element to suppress unknown-element warnings:
// vite.config.ts
plugins: [
vue({
template: {
compilerOptions: { isCustomElement: (tag) => tag.startsWith('sp-') }
}
})
]<sp-input name="email" :invalid="hasError" @change="handleChange" />Astro — use components as static custom elements or with client:load when
JavaScript interactivity is needed:
---
import '@phcdevworks/spectre-tokens/index.css';
import '@phcdevworks/spectre-ui/index.css';
---
<script>
import { defineSpectreComponents } from '@phcdevworks/spectre-components';
defineSpectreComponents();
</script>
<sp-button variant="primary">Click me</sp-button>Framework adapter packages that wrap these components into idiomatic JSX or SFC APIs belong in a downstream adapter — not in this package.
Accessibility
All components follow WCAG 2.1 AA baseline expectations by default.
ARIA attribute forwarding — aria-label, aria-labelledby, and
aria-describedby set on the host element are automatically forwarded to the
inner native element so screen readers receive them on the correct target.
Native element semantics — every component renders a real native element
(<button>, <input>, <textarea>, <select>, <label>, <fieldset>) or a
semantic light-DOM container with forwarded ARIA attributes, so browser
accessibility APIs work without customization.
State communication
| State | ARIA effect |
| ---------- | ---------------------------------------------------- |
| loading | aria-busy="true" on the native element |
| invalid | aria-invalid="true" on the native element |
| disabled | native disabled attribute (removes from tab order) |
| required | native required attribute |
Focus delegation — .focus() and .blur() called on the host are delegated
to the inner native element so external focus() calls work as expected.
Label association — use <sp-label for="id"> paired with id on the target
control, or wrap controls inside a <sp-fieldset>. The for attribute forwards
to the native <label> element.
Keyboard behavior — provided entirely by the native element inside each component. No custom keyboard handling is layered on top.
Light DOM Rendering
All components render in light DOM (createRenderRoot() { return this; }).
This is intentional: it allows @phcdevworks/spectre-ui global CSS to reach the
native element directly without Shadow DOM piercing.
As a result, these components have no ::part() exports — the native element is
directly selectable using standard CSS combinators or the stable internal data
attributes:
/* Target the native input inside sp-input */
sp-input input {
font-size: 0.875rem;
}
/* Stable internal hook — won't break if markup restructures */
sp-input [data-sp-input-native] {
font-size: 0.875rem;
}Do not switch any component from light DOM to Shadow DOM without a design-system-level decision.
Components
sp-button
Renders a <button> with Spectre variant, size, loading, and pill support. Set
href to render a native <a> instead, styled with the same classes — useful
when the button needs to navigate rather than submit/act.
Attributes
| Attribute | Type | Default | Description |
| ------------------ | -------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------- |
| variant | primary \| secondary \| ghost \| danger \| success \| cta \| accent \| inverse | primary | Visual style |
| size | sm \| md \| lg | md | Control size |
| type | button \| submit \| reset | button | Native button type (ignored when rendered as a link) |
| href | string | — | Renders <a href> instead of <button> (unless disabled/loading) |
| target | _blank \| _self \| _parent \| _top | — | Forwarded to the native <a> when href is set |
| rel | string | — | Forwarded to the native <a> when href is set |
| label | string | — | Text label (overridden by content projection) |
| loading | boolean | false | Busy state — disables the button/link and shows loading label |
| loading-label | string | Loading | Accessible text shown during loading |
| disabled | boolean | false | Disables the button; if href is also set, still renders <button disabled> |
| full-width | boolean | false | Spans full container width |
| pill | boolean | false | Pill / fully-rounded corners |
| compact | boolean | false | Denser padding/height variant |
| inner-class | string | — | Spectre utility classes applied to the native <button>/<a> |
| name | string | — | Form field name |
| value | string | '' | Submitted value |
| form | string | — | Associates with a form by ID |
| autofocus | boolean | false | Autofocus on page load |
| id | string | — | Forwarded to the native element |
| title | string | — | Forwarded to the native element |
| aria-label | string | — | Forwarded to the native element |
| aria-labelledby | string | — | Forwarded to the native element |
| aria-describedby | string | — | Forwarded to the native element |
Events — native button/link events bubble normally (click, focus,
blur).
Content projection — place children inside <sp-button> to use them as
button content instead of the label property:
<sp-button variant="primary">
<svg aria-hidden="true">...</svg>
Save changes
</sp-button>Link mode:
<sp-button variant="secondary" href="/pricing" target="_blank" rel="noopener">
View pricing
</sp-button>Internal target — [data-sp-button-native] selects the native <button> or
<a>.
sp-input
Renders an <input> with state, size, and type support.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ----------------------------- |
| type | text \| email \| password \| search \| tel \| url \| number \| date \| datetime-local \| month \| time \| week | text | Native input type |
| size | sm \| md \| lg | md | Control size |
| name | string | — | Form field name |
| value | string | '' | Current value |
| placeholder | string | — | Placeholder text |
| disabled | boolean | false | Disables the input |
| loading | boolean | false | Busy state |
| readonly | boolean | false | Read-only mode |
| required | boolean | false | Marks field as required |
| invalid | boolean | false | Error state (aria-invalid) |
| success | boolean | false | Success state |
| full-width | boolean | false | Spans full container width |
| pill | boolean | false | Pill / fully-rounded corners |
| autocomplete | string | — | Native autocomplete hint |
| inputmode | string | — | Virtual keyboard hint |
| min / max / step | string | — | Numeric/date range |
| minlength / maxlength | number | — | Character length constraints |
| form | string | — | Associates with a form by ID |
| autofocus | boolean | false | Autofocus on page load |
| id / title / aria-* | string | — | Forwarded to native <input> |
Events — input and change fire from the native <input> and bubble.
Internal target — [data-sp-input-native] selects the native <input>.
sp-textarea
Renders a <textarea> with row control and resize support.
Attributes — same as sp-input except no type, min, max, step, and
adds:
| Attribute | Type | Default | Description |
| --------- | ------ | ------- | ------------------ |
| rows | number | 2 | Visible row height |
Events — input and change fire from the native <textarea>.
Internal target — [data-sp-textarea-native] selects the native
<textarea>.
sp-select
Renders a <select>. Pass <option> elements as children — they are projected
into the native select element.
Attributes — same as sp-input minus type, placeholder, readonly,
inputmode, min, max, step, minlength, maxlength.
Events — input and change fire from the native <select>.
Content projection — <option> and <optgroup> children are moved into the
native <select>:
<sp-select name="country" required>
<option value="">Select a country</option>
<optgroup label="Americas">
<option value="us">United States</option>
<option value="ca">Canada</option>
</optgroup>
</sp-select>Internal target — [data-sp-select-native] selects the native <select>.
sp-checkbox
Renders a <label> wrapping an <input type="checkbox"> with indicator.
Attributes
| Attribute | Type | Default | Description |
| ------------------------------------------------ | ------- | ------- | --------------------------------------------- |
| name | string | — | Form field name |
| value | string | on | Submitted value when checked |
| checked | boolean | false | Checked state |
| label | string | — | Text label (overridden by content projection) |
| disabled | boolean | false | Disables the checkbox |
| loading | boolean | false | Busy state |
| required | boolean | false | Marks field as required |
| invalid | boolean | false | Error state |
| success | boolean | false | Success state |
| form / autofocus / id / title / aria-* | — | — | Forwarded to native <input> |
Events — input and change fire from the native checkbox input.
Content projection — children become the label content (supports rich markup):
<sp-checkbox name="terms" value="accepted" required>
I accept the <a href="/terms">terms of service</a>
</sp-checkbox>Internal target — [data-sp-checkbox-native] selects the native checkbox.
sp-radio
Renders a <label> wrapping an <input type="radio"> with indicator. Group
multiple sp-radio elements by giving them the same name.
Attributes — same as sp-checkbox. value defaults to on.
Events — input and change fire from the native radio input.
Content projection — same as sp-checkbox.
<sp-radio name="plan" value="monthly">Monthly — $9/mo</sp-radio>
<sp-radio name="plan" value="annual">Annual — $90/yr</sp-radio>Internal target — [data-sp-radio-native] selects the native radio input.
sp-label
Renders a <label> with for forwarding. Use to associate a visible label with
any form control.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------ | ------- | ------------------------------------------------------------ |
| for | string | — | ID of the associated control (forwarded to native <label>) |
| id / title / aria-* | string | — | Forwarded to native <label> |
Content projection — children become the label text (supports rich markup):
<sp-label for="email">
Email address <span aria-hidden="true">*</span>
</sp-label>Internal target — [data-sp-label-native] selects the native <label>.
sp-fieldset
Renders a <fieldset> with optional legend and group-level state.
Attributes
| Attribute | Type | Default | Description |
| ------------------------------------------- | ------- | ------- | ---------------------------------- |
| legend | string | — | Text for the <legend> element |
| disabled | boolean | false | Disables all controls in the group |
| loading | boolean | false | Busy state |
| invalid | boolean | false | Group-level error state |
| success | boolean | false | Group-level success state |
| form / name / id / title / aria-* | string | — | Forwarded to native <fieldset> |
Content projection — children are placed inside the native <fieldset>
alongside the legend:
<sp-fieldset legend="Billing address" name="billing">
<sp-label for="city">City</sp-label>
<sp-input id="city" name="city" required></sp-input>
<sp-label for="zip">ZIP code</sp-label>
<sp-input id="zip" name="zip" type="text" maxlength="10"></sp-input>
</sp-fieldset>Internal target — [data-sp-fieldset-native] selects the native
<fieldset>.
sp-badge
Renders a <span> display primitive backed by the Spectre badge recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------ |
| variant | primary \| secondary \| ghost \| danger \| success \| warning \| info \| accent \| cta \| neutral \| outline \| inverse | primary | Visual style |
| size | sm \| md \| lg | md | Badge size |
| accent-rail | top \| right \| bottom \| left | — | Optional decorative edge-rail; omitted renders no rail. Distinct from variant: 'accent', an unrelated single-tone fill |
| accent-rail-color | neutral \| brand \| info \| success \| warning \| danger \| cta | brand | Accent rail color; only applied when accent-rail is set |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| full-width | boolean | false | Spans full container width |
| inner-class | string | — | Spectre utility classes applied to the native <span> |
| id / title / aria-* | string | — | Forwarded to the native <span> |
Content projection — children become the badge content.
Internal target — [data-sp-badge-native] selects the native <span>.
sp-card
Renders a <div> container backed by the Spectre card recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ----------------------------------------------------------------- | ---------- | ------------------------------------------------------------- |
| variant | elevated \| flat \| outline \| ghost | elevated | Visual style |
| padded | boolean \| 'sm' \| 'md' \| 'lg' | true | Card padding step; false opts out, true/"md" is default |
| accent | top \| right \| bottom \| left | — | Optional decorative edge-rail; omitted renders no rail |
| accent-color | neutral \| brand \| info \| success \| warning \| danger \| cta | brand | Accent rail color; only applied when accent is set |
| full-height | boolean | false | Spans full container height |
| interactive | boolean | false | Applies interactive styling |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| inner-class | string | — | Spectre utility classes applied to the native <div> |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — children become the card content.
Internal target — [data-sp-card-native] selects the native <div>.
sp-icon-box
Renders a <div> icon container backed by the Spectre icon-box recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | -------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------- |
| variant | primary \| secondary \| ghost \| danger \| success \| warning \| info \| accent \| cta \| neutral \| outline | primary | Visual style |
| size | sm \| md \| lg | md | Icon-box size |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| interactive | boolean | false | Applies interactive styling |
| pill | boolean | false | Pill / fully-rounded corners |
| full-width | boolean | false | Spans full container width |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — children become the icon-box content.
Internal target — [data-sp-icon-box-native] selects the native <div>.
sp-rating
Renders a read-only rating visualization with generated star spans.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ---------------- | ------- | -------------------------------------- |
| value | number | 0 | Filled star count |
| max | number | 5 | Total star count |
| size | sm \| md \| lg | md | Rating size |
| label | string | — | Optional visible text beside the stars |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| id / title / aria-* | string | — | Forwarded to the rating container |
Accessibility — renders role="img" and computes an accessible label like
Rating: 4 out of 5 unless aria-label is provided.
Internal target — [data-sp-rating-native] selects the rating container.
sp-testimonial
Renders a <div> testimonial container backed by the Spectre testimonial
recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ----------------------------------------------------------------- | ---------- | ------------------------------------------------------ |
| variant | elevated \| flat \| outline \| ghost | elevated | Visual style |
| accent | top \| right \| bottom \| left | — | Optional decorative edge-rail; omitted renders no rail |
| accent-color | neutral \| brand \| info \| success \| warning \| danger \| cta | brand | Accent rail color; only applied when accent is set |
| full-height | boolean | false | Spans full container height |
| interactive | boolean | false | Applies interactive styling |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — children become the testimonial content.
Internal target — [data-sp-testimonial-native] selects the native <div>.
sp-alert
Renders a <div role="alert"> display primitive backed by the Spectre alert
recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ---------------------------------------------------------- | --------- | ----------------------------------------------- |
| variant | info \| success \| warning \| danger \| neutral \| brand | info | Visual style |
| size | sm \| md \| lg | md | Alert size |
| dismissible | boolean | false | Renders a close button that dismisses the alert |
| dismiss-label | string | Dismiss | Accessible name of the close button |
| dismissed | boolean | false | Dismissed (hidden) state |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| full-width | boolean | false | Spans full container width |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — an element with slot="icon" renders in the leading
icon slot, colored by the variant; all other children become the alert content.
Accessibility — renders role="alert" and reflects the loading state to
aria-busy.
Events — sp-dismiss (bubbling) when the close button dismisses the alert.
Internal targets — [data-sp-alert-native] selects the native <div>;
[data-sp-alert-dismiss] selects the close button.
sp-avatar
Renders a <div> avatar container backed by the Spectre avatar recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ---------------------------- | -------- | -------------------------------- |
| size | xs \| sm \| md \| lg \| xl | md | Avatar size |
| shape | circle \| square | circle | Avatar shape |
| interactive | boolean | false | Applies interactive styling |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| full-width | boolean | false | Spans full container width |
| placeholder | boolean | false | Placeholder background and color |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — children become the avatar content (an <img>,
initials, or an icon).
Accessibility — reflects the loading state to aria-busy.
Internal target — [data-sp-avatar-native] selects the native <div>.
sp-spinner
Renders a <div role="status"> loading indicator backed by the Spectre spinner
recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------------------------------------------------------------------------------------------ | ------- | ------------------------------- |
| variant | primary \| secondary \| success \| warning \| danger \| info \| neutral \| accent \| cta | — | Arc color |
| size | sm \| md \| lg | md | Spinner size |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | true | Busy visual state |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Accessibility — renders role="status" and reflects the loading state to
aria-busy. Defaults aria-label to Loading unless aria-label is provided.
Internal target — [data-sp-spinner-native] selects the native <div>.
sp-tag
Renders a <span> tag/chip backed by the Spectre tag recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------- |
| variant | default \| primary \| secondary \| success \| warning \| danger \| info \| neutral \| accent \| cta \| outline \| ghost | default | Tag color |
| size | sm \| md \| lg | md | Tag size |
| interactive | boolean | false | Applies interactive styling |
| selected | boolean | false | Selected/active visual state |
| dismissible | boolean | false | Reserves space for a dismiss icon |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| full-width | boolean | false | Spans full container width |
| id / title / aria-* | string | — | Forwarded to the native <span> |
Content projection — children become the tag label (and any projected dismiss icon).
Accessibility — reflects the loading state to aria-busy.
Internal target — [data-sp-tag-native] selects the native <span>.
sp-pricing-card
Renders a <div> pricing card container backed by the Spectre pricing-card
recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ----------------------------------------------------------------- | ------- | ------------------------------------------------------ |
| featured | boolean | false | Highlights the card as featured |
| accent | top \| right \| bottom \| left | — | Optional decorative edge-rail; omitted renders no rail |
| accent-color | neutral \| brand \| info \| success \| warning \| danger \| cta | brand | Accent rail color; only applied when accent is set |
| interactive | boolean | false | Applies interactive styling |
| disabled | boolean | false | Disabled visual state |
| loading | boolean | false | Busy visual state |
| full-height | boolean | false | Spans full container height |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — children become the pricing card content (heading, price, feature list, call-to-action, etc.).
Accessibility — reflects the loading state to aria-busy.
Internal target — [data-sp-pricing-card-native] selects the native
<div>.
Layout components
sp-container, sp-grid, sp-section, sp-stack, sp-footer, sp-nav, and
sp-logo-cloud share two contracts:
- Host display — the host element defaults to
display: block(set via inline style inconnectedCallback, so a consumer's ownstyle="display: ..."always wins) instead of the browser's default inline custom-element box. This keeps backgrounds, margins, and full-width inner content from being trapped inside an inline box. inner-class— aninner-classattribute (innerClassJS property) appends consumer-supplied Spectre utility classes to the native inner element the component's recipe classes render on, without touching the host's ownclassattribute. Hostclassandinner-classare distinct targets: hostclassaffects the custom-element box itself,inner-classaffects the styled element inside it. Only tokens matchingsp-*(Spectre utility class syntax) are applied; anything else is silently dropped.
<sp-stack class="my-host-hook" inner-class="sp-bg-primary-500 sp-p-8">
...
</sp-stack>Spacing steps and the 8px layout grid — sp-section spacing/gap,
sp-stack gap, sp-grid gap/row-gap/column-gap, and sp-container
padding share one step scale: sm | md | lg | xl | 2xl | 3xl | 4xl. Every
step resolves to a layout.* token on the 8px layout grid (4px is reserved for
spacing inside a component), and the xl–4xl steps widen at the lg
breakpoint through the token package's responsive remap. Set larger or
viewport-responsive spacing through these attributes, never with sp-gap-*,
sp-p*-*, or raw length overrides.
<sp-section spacing="3xl" gap="2xl">
<sp-grid columns="3" gap="xl">...</sp-grid>
</sp-section>sp-container
Renders a <div> layout container backed by the Spectre container recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------------------------------------------- | ------- | ----------------------------------------------------- |
| max-width | prose | — | Constrains content to a max width |
| padding | sm \| md \| lg \| xl \| 2xl \| 3xl \| 4xl | md | Inline padding step |
| inner-class | string | — | Spectre utility classes applied to the native <div> |
| id / title / aria-* | string | — | Forwarded to the native <div> |
Content projection — children become the container content.
Internal target — [data-sp-container-native] selects the native <div>.
sp-grid
Renders a <div> grid layout backed by the Spectre grid recipe.
Attributes
| Attribute | Type | Default | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------- |
| columns | 1 \| 2 \| 3 \| 4 \| 6 \| 12 | 1 | Number of grid columns |
| gap | sm \| md \| lg \| xl \| 2xl \| 3xl \| 4xl | md | Gap between grid items |
| align | start \| center \| end \| baseline \| stretch | — | Cross-axis alignment of grid items |
| span | 1-12 \| 'full' or { base?, md?, lg? } (JS property only) | — | Column span for a grid item, single value or per-breakpoint |
| column-gap | any gap step (JS: columnGap) | — | Overrides gap on the column axis only |
| row-gap | any gap step (JS: rowGap) | — | Overrides gap on the row axis only |
| offset | 0-11 or { base?, md?, lg? } (JS property only) | — | Column offset for a grid item, single value or per-breakpoint |
| row-span | 1-12 \| 'full' or { base?, md?, lg? } (JS: rowSpan) | — | Row span for a grid item, single value or per-breakpoint |
| row-offset | 0-11 or { base?, md?, lg? } (JS: rowOffset) | — | Row offset for a grid item, single value or per-breakpoint |
| order | 'first' \| 'last' \| 'none' \| 1-12 or { base?, md?, lg? } (JS property only) | — | Reorders a grid item independent of source order |
| leading-tracks | { weight: 1.5\|1.6\|2\|2.5\|3 \| { base?, md?, lg? } } (JS: leadingTracks) | — | One wider leading column plus columns - 1 equal columns |
| fixed-tracks | { count: 1\|2\|3\|4 } (JS: fixedTracks) | — | Fixed-width repeated tracks (--sp-space-240), replaces columns |
| explicit-template | { template: 'edge-fluid-edge'\|'label-fluid-fluid', weight? } (JS: explicitTemplate) | — | Named asymmetric column shape; replaces columns/leadingTracks/fixedTracks |
| inner-class | string | — | Spectre utility classes applied to the native <div> |
| id / title / aria-* / role | string | — | Forwarded to the native <div> |
leading-tracks, fixed-tracks, explicit-template, and any per-breakpoint
{ base?, md?, lg? } shape are JS-property-only (set via the DOM property, not
an HTML attribute string).
Setting role (e.g. role="table") reflects it directly onto the native
<div>. sp-grid renders its light-DOM children into that single container, so
a table-shaped role structure (role="row"/role="cell" on children) is the
consumer's responsibility — sp-grid does not synthesize row/cell roles for
projected content.
Nested grids — when an sp-grid is a child of another grid, the parent lays
out the host element, so the item-placement options (span, offset,
col-start, row-span, row-offset, order) are applied as classes on the
host instead of the inner <div>. The grid's own columns and gaps stay on the
inner <div>. Classes you author on the host are left alone.
<sp-grid columns="3">
<sp-grid columns="2" span="2">...</sp-grid>
<div>Sidebar</div>
</sp-grid>align is not reflected to a host attribute: align="center" on any element is
the legacy HTML presentational hint for text-align: center.
Content projection — children become grid items.
Internal target — [data-sp-grid-native] selects the native <div>.
sp-section
Renders a <section> layout wrapper backed by the Spectre section recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------------------------------------------- | ------- | ------------------------------------------------------------------- |
| spacing | sm \| md \| lg \| xl \| 2xl \| 3xl \| 4xl | md | Symmetric block padding step |
| gap | sm \| md \| lg \| xl \| 2xl \| 3xl \| 4xl | — | Stacks direct children with this gap |
| hero | sm \| md \| lg | — | Asymmetric hero padding (more above than below); replaces spacing |
| attached | boolean | false | Drops the top padding of a band that belongs to the section above |
| inner-class | string | — | Spectre utility classes applied to the native <section> |
| id / title / aria-* | string | — | Forwarded to the native <section> |
A hero needs no sp-pt-*/sp-pb-*/sp-py-* override and no selector on the
rendered <section>; choose its size with hero. Use attached on a band such
as a logo strip under a hero, so the gap between the two is the hero's bottom
padding alone. Back-to-back sections on different surfaces keep their own
padding.
<sp-section hero="lg">...</sp-section>
<sp-section attached spacing="sm"
><sp-logo-cloud>...</sp-logo-cloud></sp-section
>Content projection — children become the section content.
Internal target — [data-sp-section-native] selects the native <section>.
sp-stack
Renders a <div> flex stack backed by the Spectre stack recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------------------------------------------- | ---------- | ----------------------------------------------------- |
| direction | vertical \| horizontal | vertical | Stack axis |
| basis | sidebar | — | Reserves sidebar-sized basis on items |
| align | center \| stretch | center | Cross-axis alignment (not reflected to the host) |
| gap | sm \| md \| lg \| xl \| 2xl \| 3xl \| 4xl | md | Gap between stack items |
| inner-class | string | — | Spectre utility classes applied to the native <div> |
| id / title / aria-* | string | — | Forwarded to the native <div> |
align is not reflected to a host attribute, because align="center" on any
element is the legacy HTML presentational hint for text-align: center. The
default no longer centers text inside the stack. Authoring align="center"
yourself still triggers the hint, and it is the default anyway, so leave it out.
Content projection — children become stack items.
Internal target — [data-sp-stack-native] selects the native <div>.
sp-footer
Renders a <footer> backed by the Spectre footer recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ----------------------------------------------------------------- | ------- | -------------------------------------------------------- |
| bordered | boolean | false | Adds a top border |
| accent | top \| right \| bottom \| left | — | Optional decorative edge-rail; omitted renders no rail |
| accent-color | neutral \| brand \| info \| success \| warning \| danger \| cta | brand | Accent rail color; only applied when accent is set |
| full-width | boolean | false | Spans full container width |
| appearance | dark \| light \| system | dark | Color palette; system follows prefers-color-scheme |
| surface | page \| card \| subtle \| inverse \| hero | — | Paints the footer on a published surface role |
| inner-class | string | — | Spectre utility classes applied to the native <footer> |
| id / title / aria-* | string | — | Forwarded to the native <footer> |
surface changes only the background. Pair it with the appearance whose text
palette suits it: light for page, card, and subtle, and the dark
default for inverse and hero. Neither option needs a selector on
.sp-footer or [data-sp-footer-native].
<sp-footer appearance="light" surface="subtle">...</sp-footer>Content projection — children become the footer content (links, legal text, etc.).
Internal target — [data-sp-footer-native] selects the native <footer>.
sp-footer-link
Renders an <a> link styled for sp-footer content, backed by the Spectre
footer link recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------- | ------- | --------------------------------------------------------------- |
| href | string | — | Link target (dropped when disabled) |
| active | boolean | false | Marks the link as the current page (aria-current="page") |
| disabled | boolean | false | Disables the link (aria-disabled, tabindex="-1", no href) |
| id / title / aria-* | string | — | Forwarded to the native <a> |
Content projection — children become the link text.
Internal target — [data-sp-footer-link-native] selects the native <a>.
<sp-footer>
<sp-footer-link href="/privacy">Privacy</sp-footer-link>
<sp-footer-link href="/terms" active>Terms</sp-footer-link>
</sp-footer>sp-footer-chip
Renders a <span> chip styled for sp-footer content (e.g. a version or status
badge), backed by the Spectre footer chip recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ------- | ------- | --------------------------------------- |
| disabled | boolean | false | Disabled visual state (aria-disabled) |
| id / title / aria-* | string | — | Forwarded to the native <span> |
Content projection — children become the chip text.
Internal target — [data-sp-footer-chip-native] selects the native
<span>.
sp-nav
Renders a <nav> backed by the Spectre nav recipe.
Attributes
| Attribute | Type | Default | Description |
| ------------------------- | ----------------------------------------------------------------- | ------- | ------------------------------------------------------ |
| bordered | boolean
