@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.
| Field | Value |
|---|---|
| Project team | project-design |
| Repository role | Spectre L3a Lit web component layer |
| Package/artifact | @phcdevworks/spectre-components |
| Current version/status | 1.21.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 |
@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
@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.
- 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
- 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.
- 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.
npm install @phcdevworks/spectre-components @phcdevworks/spectre-ui @phcdevworks/spectre-tokensImport 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="you@example.com"
></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>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()<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>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.
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.
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.
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>.
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>.
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>.
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>.
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.
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.
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>.
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>.
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>.
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>.
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>.
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.
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>.
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.
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>.
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>.
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>.
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>.
sp-container, sp-grid, sp-section, sp-stack, sp-footer, and sp-nav
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>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 |
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>.
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 |
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 |
sm | md | lg (JS: columnGap) |
— | Overrides gap on the column axis only |
row-gap |
sm | md | lg (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.
Content projection — children become grid items.
Internal target — [data-sp-grid-native] selects the native <div>.
Renders a <section> layout wrapper backed by the Spectre section recipe.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
inner-class |
string | — | Spectre utility classes applied to the native <section> |
id / title / aria-* |
string | — | Forwarded to the native <section> |
Content projection — children become the section content.
Internal target — [data-sp-section-native] selects the native <section>.
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 |
gap |
sm | md | lg |
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> |
Content projection — children become stack items.
Internal target — [data-sp-stack-native] selects the native <div>.
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 |
inner-class |
string | — | Spectre utility classes applied to the native <footer> |
id / title / aria-* |
string | — | Forwarded to the native <footer> |
Content projection — children become the footer content (links, legal text, etc.).
Internal target — [data-sp-footer-native] selects the native <footer>.
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>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>.
Renders a <nav> backed by the Spectre nav recipe.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
bordered |
boolean | false |
Adds a bottom 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 |
sticky |
boolean | false |
Sticks the nav to the viewport |
full-width |
boolean | false |
Spans full container width |
align |
start | center | end |
— | Horizontal alignment of nav content within the bar |
inner-class |
string | — | Spectre utility classes applied to the native <nav> |
id / title / aria-* |
string | — | Forwarded to the native <nav> |
Content projection — children become the nav content (links, brand mark, etc.).
Internal target — [data-sp-nav-native] selects the native <nav>.
Renders a nav link that can optionally become a dropdown trigger, backed by the
same Spectre dropdown recipe as sp-dropdown. Place it inside sp-nav
alongside plain <a> links.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
dropdown |
boolean | false |
Renders a dropdown trigger + menu instead of a plain <a> |
href |
string | — | Link target when dropdown is false |
label |
string | — | Trigger/link text when no content is projected |
mega |
boolean | false |
Anchors the menu to the nearest positioned ancestor instead of the trigger, spanning its full width (dropdown mode only) |
open |
boolean | false |
Open/closed menu state (dropdown mode only) |
placement |
string | 'bottom-start' |
Menu position: bottom-start, bottom-end, top-start, top-end |
id / title / aria-* |
string | — | Forwarded to the rendered <a> or trigger <button> |
Content projection — in dropdown mode, children become the menu content; a
child with slot="trigger" becomes the trigger content instead of label. Menu
content can be a plain list of links or an sp-grid for a full mega-menu
layout. In link mode, children become the link's content.
Events — sp-open and sp-close (dropdown mode only), both bubbles.
Internal targets — [data-sp-nav-item-trigger] selects the trigger button;
[data-sp-nav-item-menu] selects the menu panel.
<sp-nav>
<sp-nav-item href="/">Home</sp-nav-item>
<sp-nav-item dropdown label="Products">
<sp-grid columns="3" gap="lg">
<div>Column one</div>
<div>Column two</div>
<div>Column three</div>
</sp-grid>
</sp-nav-item>
<sp-nav-item dropdown mega label="Solutions">
<sp-grid columns="4" gap="lg">
<div>Column one</div>
<div>Column two</div>
<div>Column three</div>
<div>Column four</div>
</sp-grid>
</sp-nav-item>
</sp-nav>mega requires a positioned ancestor to anchor against — sp-nav is a
positioning context by default, so nesting inside it is sufficient.
Renders an off-canvas <aside> with a toggle button and backdrop, backed by the
Spectre sidebar recipe.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
bordered |
boolean | false |
Adds a trailing border |
hide-toggle |
boolean | false |
Suppresses the built-in toggle button (e.g. when using sp-sidebar-toggle elsewhere) |
open |
boolean | false |
Open/closed off-canvas state |
toggle-label |
string | Toggle sidebar |
Accessible label for the built-in toggle button |
id / title / aria-* |
string | — | Forwarded to the native <aside> |
Content projection — children become the sidebar content (links, navigation groups, etc.).
Behavior — clicking the toggle button opens/closes the sidebar; clicking the
backdrop or pressing Esc closes it. Toggling sets data-sidebar-open
("true"/"false") on the host element, which the Spectre CSS uses to show or
hide the off-canvas panel and backdrop.
Events — sp-open and sp-close, both bubbling CustomEvents with no
detail.
Internal target — [data-sp-sidebar-native] selects the native <aside>.
Renders an <a> link styled for sp-sidebar navigation content, backed by the
Spectre sidebar 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) |
level |
parent | child |
parent |
Indents/styles the link as a nested (child) sidebar link |
id / title / aria-* |
string | — | Forwarded to the native <a> |
Content projection — children become the link text.
Internal target — [data-sp-sidebar-link-native] selects the native <a>.
<sp-sidebar>
<sp-sidebar-link href="/dashboard" active>Dashboard</sp-sidebar-link>
<sp-sidebar-link href="/settings">Settings</sp-sidebar-link>
<sp-sidebar-link href="/settings/billing" level="child"
>Billing</sp-sidebar-link
>
</sp-sidebar>Renders a standalone trigger button that opens/closes a remote sp-sidebar,
backed by the Spectre sidebar-toggle recipe. Use it to place a toggle somewhere
other than inside the sidebar itself (a header, a nav bar) — pair it with
hide-toggle on the target sp-sidebar to suppress that sidebar's own built-in
toggle, or leave both in place to control the same sidebar from two triggers.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
for |
string | — | id of the target sp-sidebar's native content (the id set on the sp-sidebar element itself) |
label |
string | Toggle sidebar |
Accessible label when no icon content is projected |
id / title / aria-* |
string | — | Forwarded to the native <button> |
Content projection — children become the button's icon content; falls back
to a default ☰ glyph when empty.
Behavior — clicking the button toggles the target sp-sidebar's open
property and mirrors its own aria-expanded state, including staying in sync
when the target is opened/closed by another trigger (its own built-in toggle,
the backdrop, Esc, or a second sp-sidebar-toggle).
<sp-nav>
<sp-sidebar-toggle for="app-sidebar"></sp-sidebar-toggle>
</sp-nav>
<sp-sidebar id="app-sidebar" hide-toggle>
<a href="/dashboard">Dashboard</a>
</sp-sidebar>Internal target — [data-sp-sidebar-toggle-native] selects the native
<button>.
Renders a trigger button and a menu container, backed by the Spectre dropdown recipes.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
open |
boolean | false |
Open/closed menu state |
placement |
bottom-start | bottom-end | top-start | top-end |
bottom-start |
Menu position relative to trigger |
accent |
top | right | bottom | left |
— | Optional decorative edge-rail on the menu; 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 |
mega |
boolean | false |
Anchors the menu to the nearest positioned ancestor instead of the trigger, spanning its full width |
viewport |
boolean | false |
Breaks the menu out to the full browser viewport width; takes precedence over mega if both are set |
trigger-label |
string | Toggle menu |
Visible/accessible trigger text when no slot="trigger" content is projected |
id / title / aria-* |
string | — | Forwarded to the trigger button |
Content projection — an element with slot="trigger" becomes the trigger
button content; all other children become the menu content.
Behavior — clicking the trigger toggles the menu; clicking outside the
component or pressing Esc closes it and returns focus to the trigger.
Events — sp-open and sp-close, both bubbling CustomEvents with no
detail.
Internal targets — [data-sp-dropdown-trigger] selects the trigger button,
[data-sp-dropdown-menu] selects the menu container.
Renders a full-screen overlay and dialog, backed by the Spectre modal recipes.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
open |
boolean | false |
Open/closed dialog state |
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 |
id / title / aria-* |
string | — | Forwarded to the native dialog element |
Content projection — children become the dialog content.
Behavior — traps Tab/Shift+Tab focus within the dialog while open,
closes on Esc or a backdrop click, focuses the first focusable element on
open, and restores focus to the previously focused element on close.
Events — sp-close, a bubbling CustomEvent with no detail.
Internal target — [data-sp-modal-native] selects the native dialog
element; [data-sp-modal-overlay] selects the overlay backdrop.
Renders a <div role="status"> notification, backed by the Spectre toast
recipes, with an imperative show/dismiss API.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
variant |
info | success | warning | danger |
info |
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 |
dismissed |
boolean | false |
Dismissed visual state |
full-width |
boolean | false |
Spans full container width |
auto-dismiss |
number | — | Milliseconds before auto-dismissing |
id / title / aria-* |
string | — | Forwarded to the native <div> |
Content projection — an element with slot="icon" becomes the toast icon;
all other children become the toast body content.
Methods — show() and dismiss() toggle the dismissed state
imperatively.
Events — sp-show and sp-dismiss, both bubbling CustomEvents with no
detail.
Accessibility — renders role="status" with aria-live="polite" and
aria-atomic="true".
Internal target — [data-sp-toast-native] selects the native <div>.
Renders a trigger wrapper and a role="tooltip" body, backed by the Spectre
tooltip recipe.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
placement |
top | bottom | left | right |
top |
Tooltip position relative to trigger |
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 |
visible |
boolean | false |
Visible/hidden tooltip state |
id / title / aria-* |
string | — | Forwarded to the tooltip body |
Content projection — an element with slot="tooltip" becomes the tooltip
body; all other children become the trigger content.
Behavior — becomes visible on trigger mouseenter/focusin and hides on
mouseleave/focusout.
Events — sp-show and sp-hide, both bubbling CustomEvents with no
detail.
Internal target — [data-sp-tooltip-native] selects the native tooltip
body.
Renders a text primitive backed by the Spectre text recipe. The rendered tag
switches with level while the recipe call and styling stay the same.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
level |
h1 | h2 | h3 | h4 | h5 | h6 | p | span |
p |
Rendered element tag |
size |
xs | sm | md | lg | xl | 2xl | 3xl | 4xl | 5xl | 6xl |
md |
Text size scale |
variant |
default | muted | subtle | meta | brand | onInverse | onInverseMuted |
default |
Text color role |
family |
sans | serif | mono |
— | Optional font family override |
transform |
none | uppercase | lowercase | capitalize |
— | Optional text transform |
id / title / aria-* |
string | — | Forwarded to the rendered native element |
Content projection — children become the text content.
Internal target — [data-sp-text-native] selects the rendered native
element.
Renders an ARIA tablist from its sp-tab-panel children, backed by the Spectre
tabs recipes. Each panel supplies its tab's label; sp-tabs renders the tab
buttons and owns selection.
<sp-tabs aria-label="Account settings">
<sp-tab-panel label="Profile">Profile settings</sp-tab-panel>
<sp-tab-panel label="Security">Security settings</sp-tab-panel>
</sp-tabs>sp-tabs attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
selected-index |
number | 0 |
Selected panel; a disabled or missing panel falls back to the first enabled one |
variant |
line | pill |
line |
Indicator-edge or segmented-control treatment |
vertical |
boolean | false |
Places the tab list beside the panels |
full-width |
boolean | false |
Tabs share the list width equally |
aria-label |
string | — | Forwarded to the tablist |
id / title |
string | — | Forwarded to the tabs container |
sp-tab-panel attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string | — | Tab button text |
disabled |
boolean | false |
Disables the tab |
selected |
boolean | false |
Reflects selection; set by the parent sp-tabs, not by authors |
id / title |
string | — | Forwarded to the native role="tabpanel" element |
Behavior — follows the WAI-ARIA tabs pattern with automatic activation:
roving tabindex, ArrowLeft/ArrowRight (ArrowUp/ArrowDown when
vertical), Home, and End, skipping disabled tabs.
Events — sp-change (bubbling) with detail: { index } when the user
selects a tab.
Internal targets — [data-sp-tabs-native] selects the tabs container,
[data-sp-tabs-tab] each tab button, and [data-sp-tab-panel-native] each
panel.
Renders a stack of disclosure items backed by the Spectre accordion recipes.
<sp-accordion>
<sp-accordion-item label="Shipping" open>Ships in 2 days.</sp-accordion-item>
<sp-accordion-item label="Returns">30 day returns.</sp-accordion-item>
</sp-accordion>sp-accordion attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
flush |
boolean | false |
Drops the outer border and radius |
multiple |
boolean | false |
Lets several items stay open; otherwise opening one closes the others |
id / title |
string | — | Forwarded to the accordion container |
sp-accordion-item attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string | — | Header text |
open |
boolean | false |
Expanded state |
disabled |
boolean | false |
Prevents toggling |
id / title / aria-* |
string | — | Forwarded to the header button |
Content projection — an element with slot="header" replaces the label
text in the header; all other children become the panel content.
Accessibility — the header is a native <button> with aria-expanded and
aria-controls; the panel is a role="region" labelled by its header.
Events — sp-open and sp-close (bubbling) from the item when the user
toggles it. The single-open behavior responds to user toggles only; setting
open programmatically does not close sibling items.
Host classes — the item host carries the sp-accordion__item recipe
classes (author classes are preserved) so the recipe's divider between sibling
items applies.
Internal targets — [data-sp-accordion-native],
[data-sp-accordion-item-header], [data-sp-accordion-item-panel].
Renders a <nav> breadcrumb trail backed by the Spectre breadcrumb recipes.
Each child element becomes one item; the last one is the current page.
<sp-breadcrumb>
<a href="/">Home</a>
<a href="/library">Library</a>
<span>Data</span>
</sp-breadcrumb>| Attribute | Type | Default | Description |
|---|---|---|---|
separator |
string | — | Replaces the built-in / with a decorative (aria-hidden) separator |
aria-label |
string | Breadcrumb |
Forwarded to the <nav> |
id / title |
string | — | Forwarded to the <nav> |
Accessibility — renders <nav> › <ol> › <li>; the last item gets
aria-current="page". Projected <a> children receive the
sp-breadcrumb__link class.
Internal target — [data-sp-breadcrumb-native] selects the <nav>.
Renders a list group backed by the Spectre list-group recipes. Each
sp-list-group-item becomes a native row: a <li> when every item is static,
or an <a> (with href), <button> (with interactive), or <div>
otherwise.
<sp-list-group aria-label="Mailboxes">
<sp-list-group-item href="/inbox" active>Inbox</sp-list-group-item>
<sp-list-group-item interactive>Archive</sp-list-group-item>
</sp-list-group>sp-list-group attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
flush |
boolean | false |
Drops the outer border and radius |
horizontal |
boolean | false |
Lays rows out in a row |
accent |
top | right | bottom | left |
— | Optional decorative edge-rail |
accent-color |
neutral | brand | info | success | warning | danger | cta |
brand |
Accent rail color; only applied with accent |
id / title / aria-* |
string | — | Forwarded to the list container |
sp-list-group-item attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
href |
string | — | Renders the row as a link |
target |
string | — | Link target |
interactive |
boolean | false |
Renders the row as a button |
active |
boolean | false |
Current item; sets aria-current="true" |
selected |
boolean | false |
Subtle checked tint, distinct from active |
disabled |
boolean | false |
Disables a button row; removes a link row's href |
id / title / aria-label |
string | — | Forwarded to the rendered row |
Content projection — the item host (display: contents) moves inside the
rendered row, so its children, including later edits, render in place. Use the
sp-list-group__heading / sp-list-group__text classes for two-line rows.
Events — sp-select (bubbling) from the item when a link or button row is
activated.
Internal targets — [data-sp-list-group-native] selects the list
container, [data-sp-list-group-row] each row.
Renders a slide-in dialog panel and backdrop backed by the Spectre offcanvas recipes.
| Attribute | Type | Default | Description |
|---|---|---|---|
open |
boolean | false |
Open/closed state |
placement |
start | end | top | bottom |
start |
Viewport edge the panel slides in from |
label |
string | — | Header title; also labels the dialog |
close-label |
string | Close |
Accessible name of the header close button |
id / title / aria-* |
string | — | Forwarded to the native dialog element |
Content projection — slot="header" replaces the label title,
slot="footer" fills the footer region, and all other children become the
body.
Behavior — while open: traps Tab/Shift+Tab focus, closes on Esc, the
close button, or a backdrop click, focuses the first focusable element once
the panel is visible, and restores focus on close. The closed panel is inert.
Events — sp-close (bubbling) when the user closes it.
Internal targets — [data-sp-offcanvas-native] (dialog),
[data-sp-offcanvas-backdrop], [data-sp-offcanvas-close].
Renders a slide carousel backed by the Spectre carousel recipes. Each child element becomes one slide. The viewport is a native scroll-snap track, so it stays swipeable without script.
<sp-carousel aria-label="Featured work">
<img alt="Project one" src="one.jpg" />
<img alt="Project two" src="two.jpg" />
</sp-carousel>| Attribute | Type | Default | Description |
|---|---|---|---|
index |
number | 0 |
Active slide |
fade |
boolean | false |
Cross-fades instead of scroll-snapping |
loop |
boolean | false |
Wraps from the last slide to the first and back |
hide-controls |
boolean | false |
Hides the previous/next buttons |
hide-indicators |
boolean | false |
Hides the slide indicators |
previous-label |
string | Previous slide |
Accessible name of the previous button |
next-label |
string | Next slide |
Accessible name of the next button |
aria-label |
string | Carousel |
Forwarded to the carousel region |
id / title |
string | — | Forwarded to the carousel region |
Accessibility — follows the WAI-ARIA carousel pattern: a
role="region" with aria-roledescription="carousel", slides as
role="group" with aria-roledescription="slide" and an "n of N" label, and a
polite live region. ArrowLeft/ArrowRight move between slides. There is no
autoplay.
Events — sp-change (bubbling) with detail: { index } when the user
changes slides, including by swiping.
Internal targets — [data-sp-carousel-native],
[data-sp-carousel-viewport], [data-sp-carousel-slide],
[data-sp-carousel-prev], [data-sp-carousel-next],
[data-sp-carousel-indicator].
Styles an authored <table> with the Spectre table recipes and wraps it in the
horizontal-scroll wrapper. The table must be authored inside the component,
because the HTML parser drops table parts outside a <table>.
<sp-table striped aria-label="Team members">
<table>
<thead><tr><th scope="col">Name</th></tr></thead>
<tbody><tr><td>Ada</td></tr></tbody>
</table>
</sp-table>| Attribute | Type | Default | Description |
|---|---|---|---|
size |
sm | md |
md |
Cell density |
striped |
boolean | false |
Tints alternating body rows |
hoverable |
boolean | false |
Tints body rows under the pointer |
bordered |
boolean | false |
Borders every cell |
aria-label / aria-labelledby |
string | — | Makes the wrapper a focusable, labelled scroll region |
id / title |
string | — | Forwarded to the wrapper |
Recipe classes are added to the authored <table> alongside its own classes.
For contextual rows, use aria-selected="true" or the
sp-table__row--{neutral|info|success|warning|danger|selected} classes.
Internal target — [data-sp-table-native] selects the wrapper.
Renders page navigation backed by the Spectre pagination recipes.
<sp-pagination total="20" page="3"></sp-pagination>
<sp-pagination total="20" href-template="/posts?page={page}"></sp-pagination>| Attribute | Type | Default | Description |
|---|---|---|---|
page |
number | 1 |
Current page, clamped to 1…total |
total |
number | 1 |
Page count |
siblings |
number | 1 |
Pages shown either side of the current one before an ellipsis |
size |
sm | md | lg |
md |
Item size |
href-template |
string | — | Renders links; {page} is replaced with the page number |
previous-label |
string | Previous |
Previous control text |
next-label |
string | Next |
Next control text |
aria-label |
string | Pagination |
Forwarded to the <nav> |
id / title |
string | — | Forwarded to the <nav> |
Accessibility — the current page has aria-current="page", page items are
labelled "Page N", and boundary controls are disabled (aria-disabled without
href in link mode).
Events — sp-change (bubbling) with detail: { page } when the user picks
a page. In link mode the browser still follows the link.
Internal target — [data-sp-pagination-native] selects the <nav>.
Renders a progress stepper backed by the Spectre stepper recipes. Each child element becomes one step label.
<sp-stepper aria-label="Checkout" current="1">
<span>Cart</span>
<span>Shipping</span>
<span>Payment</span>
</sp-stepper>| Attribute | Type | Default | Description |
|---|---|---|---|
current |
number | 0 |
Active step; earlier steps are done, later ones pending |
orientation |
horizontal | vertical |
horizontal |
Layout direction |
id / title / aria-* |
string | — | Forwarded to the native <ol> |
Accessibility — renders an <ol>; the active step has
aria-current="step". Done steps show a check mark, others their number.
Setting current to the step count marks every step done.
Internal target — [data-sp-stepper-native] selects the <ol>.
Renders a native <input type="checkbox" role="switch"> inside a <label>,
backed by the Spectre switch recipe. It submits with its form like a checkbox.
| Attribute | Type | Default | Description |
|---|---|---|---|
checked |
boolean | false |
On/off state; follows user toggles |
size |
sm | md | lg |
md |
Track size |
label |
string | — | Visible label when no children are projected |
name / value / form |
string | — | Native form participation (value is on) |
disabled / required |
boolean | false |
Native constraints |
focused |
boolean | false |
Forced focus look (see Shared Conventions) |
id / title / aria-* |
string | — | Forwarded to the native input |
Content projection — children become the label text.
Renders a native <input type="range"> backed by the Spectre range recipe. It
mirrors the value into --sp-component-range-value so WebKit/Blink paint the
filled track.
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
number | 50 |
Current value, clamped to min…max |
min / max / step |
number | 0 / 100 / 1 |
Native range bounds |
name / form |
string | — | Native form participation |
disabled / focused |
boolean | false |
Disabled state / forced focus look |
id / title / aria-* |
string | — | Forwarded to the native input |
Renders a native <input type="file"> backed by the Spectre file-input recipe,
which styles the browser's file selector button. The files getter returns the
native FileList.
| Attribute | Type | Default | Description |
|---|---|---|---|
size |
sm | md | lg |
md |
Control size |
invalid / success |
boolean | false |
Validation state (invalid wins) |
accept / multiple / required |
string / boolean | — | Native file constraints |
name / form |
string | — | Native form participation |
full-width / disabled / focused |
boolean | false |
Width, disabled, forced focus look |
id / title / aria-* |
string | — | Forwarded to the native input |
Fuses addons and native controls into one bordered control, backed by the
Spectre input-group recipes. The recipe styles its direct children, so the
group takes native <input>, <select>, <button>, and file inputs rather
than sp-* wrappers. Controls without an sp-* class get their recipe class
(buttons use the secondary variant). Children marked slot="addon" become
addons. Authored order is preserved.
<sp-input-group aria-label="Handle">
<span slot="addon">@</span>
<input aria-label="Username" />
<button type="button">Check</button>
</sp-input-group>| Attribute | Type | Default | Description |
|---|---|---|---|
disabled |
boolean | false |
Disabled look for the whole group |
id / title / aria-* |
string | — | Forwarded to the role="group" wrapper |
Renders a whole-card option: a <label> wrapping a native radio or checkbox,
backed by the Spectre choice-card recipe. Selection, focus, and disabled styling
follow the native input. Radio cards that share a name keep their checked
properties in sync.
| Attribute | Type | Default | Description |
|---|---|---|---|
type |
radio | checkbox |
radio |
Native input type |
checked |
boolean | false |
Selection state |
name / value / form |
string | — | Native form participation |
disabled / required |
boolean | false |
Native constraints |
hovered / focused |
boolean | false |
Forced looks |
id / aria-describedby |
string | — | Forwarded to the native input |
Content projection — children become the card content (the input's label).
Renders a role="progressbar" track and fill backed by the Spectre progress
recipes.
| Attribute | Type | Default | Description |
|---|---|---|---|
value / max |
number | 0 / 100 |
Progress, clamped to 0…max |
variant |
brand | neutral | info | success | warning | danger |
brand |
Fill color role |
size |
sm | md | lg |
md |
Track height |
indeterminate |
boolean | false |
Animated sweep; omits aria-valuenow |
label |
string | — | Visible label above the track; labels the bar |
value-text |
string | — | aria-valuetext (e.g. "3 of 8 files") |
id / title / aria-* |
string | — | Forwarded to the track |
A click-toggled, non-modal role="dialog" anchored to its trigger, backed by
the Spectre popover recipes. Unlike sp-tooltip it stays open for interaction.
| Attribute | Type | Default | Description |
|---|---|---|---|
open |
boolean | false |
Open state |
placement |
top | bottom | left | right |
bottom |
Side the panel opens on |
label |
string | — | Header title; also labels the dialog |
trigger-label |
string | Show details |
Trigger text when no slot="trigger" content |
id / title |
string | — | Forwarded to the dialog panel |
Content projection — slot="trigger" fills the trigger button,
slot="header" replaces the label title, other children fill the body.
Behavior — the trigger toggles it (aria-expanded/aria-controls); Esc
closes it and refocuses the trigger; an outside click closes it. Without a
header the dialog is labelled by its trigger.
Events — sp-open and sp-close (bubbling).
Renders an inline calendar backed by the Spectre datepicker and day recipes.
Values are local ISO dates (YYYY-MM-DD), with no time-zone shift.
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | — | Selected date; malformed dates are dropped |
min / max |
string | — | Selectable range; days outside it are disabled |
week-start |
number | 0 |
First weekday (0 Sunday … 6 Saturday) |
locale |
string | browser locale | Month, weekday, and day-label formatting |
name |
string | — | Submits the value through a hidden input |
previous-label / next-label |
string | Previous month / Next month |
Month button labels |
aria-label |
string | month title | Labels the calendar group |
Accessibility — every day is a button labelled with its full date,
aria-pressed on the selected day and aria-current="date" on today. One day
is the tab stop (roving tabindex). Arrows move by day and week, Home/End
go to the week's start and end, and PageUp/PageDown change the month
(Shift for a year). The month title is a polite live region.
Events — sp-change (bubbling) with detail: { value }.
One neutral button treatment for every third-party sign-in provider, backed by
the Spectre external-auth-button recipes. It deliberately carries no provider
colors. Put the provider logo in slot="icon" and the label in the default
slot.
| Attribute | Type | Default | Description |
|---|---|---|---|
href |
string | — | Renders a link instead of a button |
type |
button | submit |
button |
Native button type |
full-width / disabled / loading |
boolean | false |
Width, disabled, busy states |
hovered / focused / active |
boolean | false |
Forced looks |
id / title / aria-label |
string | — | Forwarded to the native element |
A band inside sp-card (usually media) that runs flush through the card's
padding, backed by getCardBleedClasses.
| Attribute | Type | Default | Description |
|---|---|---|---|
edges |
space-separated top right bottom left, or all |
— | Edges to bleed through |
padded |
sm | md | lg, or bare |
— | The card padding step being escaped (bare means md) |
Wraps authored long-form HTML (headings, lists, code, quotes, rules) in the
Spectre prose surface. An aria-label also makes it a labelled region.
Forced interaction states — components whose recipe supports them accept
hovered, focused, and active booleans (hovered/focused only where the
recipe has no pressed state). They force the look for documentation, previews,
and visual tests; real interaction is still styled by native pseudo-classes.
Where a component already uses active to mean "current" (sp-footer-link,
sp-sidebar-link, sp-list-group-item, sp-nav-item), it keeps that meaning.
Part markers — class-only recipe parts are applied in place to authored
elements that opt in with a slot marker, keeping authored order. Nothing is
styled by tag name.
| Component | Markers |
|---|---|
sp-footer |
heading, text, muted, links, divider (any depth) |
sp-nav |
links |
sp-sidebar |
header; group on a <details> (its <summary> is styled) |
sp-dropdown / sp-nav-item menus |
item, header, divider |
sp-list-group-item |
heading, text |
sp-carousel slides |
caption (inside a slide) |
sp-table rows |
data-variant="neutral|info|success|warning|danger" on a <tr> |
Additions to existing components
| Component | Added |
|---|---|
sp-button |
warning, link, light, dark variants |
sp-badge |
brand variant, dot notification mode, interactive |
sp-spinner / sp-toast |
inverse / neutral variants |
sp-alert |
interactive |
sp-rating |
interactive, pill, full-width |
sp-text |
onSurface* variants; preset="heading | display | lead" with heading-level (h1–h6, defaults to the element level) and display-level (1–6) |
sp-container |
padding (sm | md | lg); max-width gains none and wide |
sp-section |
spacing and gap (sm | md | lg) |
sp-stack |
basis="none" |
sp-grid |
col-start (1–12, or per-breakpoint JSON like span) |
sp-nav-item |
active (current page, aria-current), disabled, and dropdown viewport, full-width, accent/accent-color |
sp-input |
label, helper-text, error-message (wraps the input only when set; an error sets the error state and aria-invalid) |
sp-select / sp-textarea |
focused |
sp-pricing-card |
badge, price, description, header, footer slots |
sp-testimonial |
quote, author-image, author-name, author-title slots |
Exports everything from all component entry points plus the bulk registration helper.
Bulk registration
import { defineSpectreComponents } from '@phcdevworks/spectre-components'
defineSpectreComponents() // registers all sp-* elementsPer-component helpers (same as individual entry points):
defineSpectreButton, defineSpectreInput, defineSpectreTextarea,
defineSpectreSelect, defineSpectreCheckbox, defineSpectreRadio,
defineSpectreLabel, defineSpectreFieldset, defineSpectreBadge,
defineSpectreCard, defineSpectreIconBox, defineSpectreRating,
defineSpectreTestimonial, defineSpectreAlert, defineSpectreAvatar,
defineSpectreSpinner, defineSpectreTag, defineSpectrePricingCard,
defineSpectreContainer, defineSpectreGrid, defineSpectreSection,
defineSpectreStack
Element classes: SpectreButtonElement, SpectreInputElement,
SpectreTextareaElement, SpectreSelectElement, SpectreCheckboxElement,
SpectreRadioElement, SpectreLabelElement, SpectreFieldsetElement,
SpectreBadgeElement, SpectreCardElement, SpectreIconBoxElement,
SpectreRatingElement, SpectreTestimonialElement, SpectreAlertElement,
SpectreAvatarElement, SpectreSpinnerElement, SpectreTagElement,
SpectrePricingCardElement, SpectreContainerElement, SpectreGridElement,
SpectreSectionElement, SpectreStackElement
Button constants and types: spectreButtonVariants, spectreButtonSizes,
spectreButtonTypes, SpectreButtonVariant, SpectreButtonSize,
SpectreButtonType, SpectreButtonProps
Input / textarea / select constants and types: spectreInputSizes,
spectreInputTypes, SpectreInputSize, SpectreInputType,
SpectreInputProps, SpectreTextareaProps, SpectreSelectProps
Props interfaces (checkbox / radio / label / fieldset):
SpectreCheckboxProps, SpectreRadioProps, SpectreLabelProps,
SpectreFieldsetProps
Display constants and types: spectreBadgeVariants, spectreBadgeSizes,
spectreCardVariants, spectreIconBoxVariants, spectreIconBoxSizes,
spectreRatingSizes, spectreTestimonialVariants, spectreAlertVariants,
spectreAlertSizes, spectreAvatarShapes, spectreAvatarSizes,
spectreSpinnerVariants, spectreSpinnerSizes, spectreTagVariants,
spectreTagSizes, SpectreBadgeVariant, SpectreBadgeSize,
SpectreCardVariant, SpectreIconBoxVariant, SpectreIconBoxSize,
SpectreRatingSize, SpectreTestimonialVariant, SpectreAlertVariant,
SpectreAlertSize, SpectreAvatarShape, SpectreAvatarSize,
SpectreSpinnerVariant, SpectreSpinnerSize, SpectreTagVariant,
SpectreTagSize, SpectreBadgeProps, SpectreCardProps,
SpectreIconBoxProps, SpectreRatingProps, SpectreTestimonialProps,
SpectreAlertProps, SpectreAvatarProps, SpectreSpinnerProps,
SpectreTagProps, SpectrePricingCardProps
Layout constants and types: spectreContainerMaxWidths,
spectreGridColumns, spectreGridGaps, spectreGridAligns,
spectreStackAligns, spectreStackBases, spectreStackDirections,
spectreStackGaps, SpectreContainerMaxWidth, SpectreGridColumns,
SpectreGridGap, SpectreGridAlign, SpectreStackAlign, SpectreStackBasis,
SpectreStackDirection, SpectreStackGap, SpectreContainerProps,
SpectreGridProps, SpectreSectionProps, SpectreStackProps
Interactive constants and types: spectreDropdownPlacements,
spectreToastVariants, spectreTooltipPlacements, SpectreDropdownPlacement,
SpectreToastVariant, SpectreTooltipPlacement, SpectreFooterProps,
SpectreNavProps, SpectreSidebarProps, SpectreSidebarToggleProps,
SpectreDropdownProps, SpectreModalProps, SpectreToastProps,
SpectreTooltipProps
Broad component inventory: defineSpectreTabs, defineSpectreTabPanel,
defineSpectreAccordion, defineSpectreAccordionItem,
defineSpectreBreadcrumb, defineSpectreListGroup,
defineSpectreListGroupItem, defineSpectreOffcanvas,
defineSpectreCarousel, defineSpectreTable, defineSpectrePagination,
defineSpectreStepper; their element classes and *Props interfaces; and
spectreTabsVariants, spectreOffcanvasPlacements, spectreTableSizes,
spectrePaginationSizes, spectreStepperOrientations with the matching
SpectreTabsVariant, SpectreOffcanvasPlacement, SpectreTableSize,
SpectrePaginationSize, and SpectreStepperOrientation types
spectre-ui 5.3.0 parity: defineSpectreSwitch, defineSpectreRange,
defineSpectreFileInput, defineSpectreInputGroup, defineSpectreChoiceCard,
defineSpectreProgress, defineSpectrePopover, defineSpectreDatepicker,
defineSpectreExternalAuthButton, defineSpectreCardBleed,
defineSpectreProse; their element classes and *Props interfaces; and
spectreProgressVariants, spectreProgressSizes, spectreSwitchSizes,
spectreFileInputSizes, spectrePopoverPlacements, spectreContainerPaddings,
spectreSectionSpacings, spectreGridColStarts, spectreTableRowVariants,
spectreTextPresets, spectreHeadingLevels, spectreDisplayLevels with their
matching types
Each entry point registers only that component and exports only its surface:
| Entry point | Registers | Key exports |
|---|---|---|
.../button |
sp-button |
defineSpectreButton, SpectreButtonElement, button constants and types |
.../input |
sp-input |
defineSpectreInput, SpectreInputElement, input constants and types |
.../textarea |
sp-textarea |
defineSpectreTextarea, SpectreTextareaElement, SpectreTextareaProps |
.../select |
sp-select |
defineSpectreSelect, SpectreSelectElement, SpectreSelectProps |
.../checkbox |
sp-checkbox |
defineSpectreCheckbox, SpectreCheckboxElement, SpectreCheckboxProps |
.../radio |
sp-radio |
defineSpectreRadio, SpectreRadioElement, SpectreRadioProps |
.../label |
sp-label |
defineSpectreLabel, SpectreLabelElement, SpectreLabelProps |
.../fieldset |
sp-fieldset |
defineSpectreFieldset, SpectreFieldsetElement, SpectreFieldsetProps |
.../badge |
sp-badge |
defineSpectreBadge, SpectreBadgeElement, badge constants and types |
.../card |
sp-card |
defineSpectreCard, SpectreCardElement, card constants and types |
.../icon-box |
sp-icon-box |
defineSpectreIconBox, SpectreIconBoxElement, icon-box constants and types |
.../rating |
sp-rating |
defineSpectreRating, SpectreRatingElement, rating constants and types |
.../testimonial |
sp-testimonial |
defineSpectreTestimonial, SpectreTestimonialElement, testimonial constants and types |
.../alert |
sp-alert |
defineSpectreAlert, SpectreAlertElement, alert constants and types |
.../avatar |
sp-avatar |
defineSpectreAvatar, SpectreAvatarElement, avatar constants and types |
.../spinner |
sp-spinner |
defineSpectreSpinner, SpectreSpinnerElement, spinner constants and types |
.../tag |
sp-tag |
defineSpectreTag, SpectreTagElement, tag constants and types |
.../pricing-card |
sp-pricing-card |
defineSpectrePricingCard, SpectrePricingCardElement, SpectrePricingCardProps |
.../container |
sp-container |
defineSpectreContainer, SpectreContainerElement, container constants and types |
.../grid |
sp-grid |
defineSpectreGrid, SpectreGridElement, grid constants and types |
.../section |
sp-section |
defineSpectreSection, SpectreSectionElement, SpectreSectionProps |
.../stack |
sp-stack |
defineSpectreStack, SpectreStackElement, stack constants and types |
.../nav |
sp-nav |
defineSpectreNav, SpectreNavElement, SpectreNavProps |
.../nav-item |
sp-nav-item |
defineSpectreNavItem, SpectreNavItemElement, SpectreNavItemProps |
.../sidebar |
sp-sidebar |
defineSpectreSidebar, SpectreSidebarElement, SpectreSidebarProps |
.../sidebar-link |
sp-sidebar-link |
defineSpectreSidebarLink, SpectreSidebarLinkElement, SpectreSidebarLinkProps |
.../sidebar-toggle |
sp-sidebar-toggle |
defineSpectreSidebarToggle, SpectreSidebarToggleElement, SpectreSidebarToggleProps |
.../dropdown |
sp-dropdown |
defineSpectreDropdown, SpectreDropdownElement, dropdown constants and types |
.../footer |
sp-footer |
defineSpectreFooter, SpectreFooterElement, SpectreFooterProps |
.../footer-link |
sp-footer-link |
defineSpectreFooterLink, SpectreFooterLinkElement, SpectreFooterLinkProps |
.../footer-chip |
sp-footer-chip |
defineSpectreFooterChip, SpectreFooterChipElement, SpectreFooterChipProps |
.../modal |
sp-modal |
defineSpectreModal, SpectreModalElement, SpectreModalProps |
.../toast |
sp-toast |
defineSpectreToast, SpectreToastElement, toast constants and types |
.../tooltip |
sp-tooltip |
defineSpectreTooltip, SpectreTooltipElement, tooltip constants and types |
.../tabs |
sp-tabs |
defineSpectreTabs, SpectreTabsElement, tabs constants and types |
.../tab-panel |
sp-tab-panel |
defineSpectreTabPanel, SpectreTabPanelElement, SpectreTabPanelProps |
.../accordion |
sp-accordion |
defineSpectreAccordion, SpectreAccordionElement, SpectreAccordionProps |
.../accordion-item |
sp-accordion-item |
defineSpectreAccordionItem, SpectreAccordionItemElement, SpectreAccordionItemProps |
.../breadcrumb |
sp-breadcrumb |
defineSpectreBreadcrumb, SpectreBreadcrumbElement, SpectreBreadcrumbProps |
.../list-group |
sp-list-group |
defineSpectreListGroup, SpectreListGroupElement, SpectreListGroupProps |
.../list-group-item |
sp-list-group-item |
defineSpectreListGroupItem, SpectreListGroupItemElement, SpectreListGroupItemProps |
.../offcanvas |
sp-offcanvas |
defineSpectreOffcanvas, SpectreOffcanvasElement, offcanvas constants and types |
.../carousel |
sp-carousel |
defineSpectreCarousel, SpectreCarouselElement, SpectreCarouselProps |
.../table |
sp-table |
defineSpectreTable, SpectreTableElement, table constants and types |
.../pagination |
sp-pagination |
defineSpectrePagination, SpectrePaginationElement, pagination constants and types |
.../stepper |
sp-stepper |
defineSpectreStepper, SpectreStepperElement, stepper constants and types |
.../card-bleed |
sp-card-bleed |
defineSpectreCardBleed, SpectreCardBleedElement, SpectreCardBleedProps |
.../prose |
sp-prose |
defineSpectreProse, SpectreProseElement, SpectreProseProps |
.../progress |
sp-progress |
defineSpectreProgress, SpectreProgressElement, progress constants and types |
.../switch |
sp-switch |
defineSpectreSwitch, SpectreSwitchElement, switch constants and types |
.../range |
sp-range |
defineSpectreRange, SpectreRangeElement, SpectreRangeProps |
.../file-input |
sp-file-input |
defineSpectreFileInput, SpectreFileInputElement, file-input constants and types |
.../external-auth-button |
sp-external-auth-button |
defineSpectreExternalAuthButton, SpectreExternalAuthButtonElement, SpectreExternalAuthButtonProps |
.../choice-card |
sp-choice-card |
defineSpectreChoiceCard, SpectreChoiceCardElement, SpectreChoiceCardProps |
.../input-group |
sp-input-group |
defineSpectreInputGroup, SpectreInputGroupElement, SpectreInputGroupProps |
.../popover |
sp-popover |
defineSpectrePopover, SpectrePopoverElement, popover constants and types |
.../datepicker |
sp-datepicker |
defineSpectreDatepicker, SpectreDatepickerElement, SpectreDatepickerProps |
Size constants are shared between input, textarea, and select. Import
spectreInputSizes / SpectreInputSize from .../input when needed alongside
textarea or select.
spectre-tokens → design values (colors, spacing, typography)
spectre-ui → CSS recipes and Tailwind helpers
spectre-components → Lit web component behavior ← you are here
[adapters] → React / Vue / Astro wrappers
The Golden Rule: tokens define meaning, UI defines structure, components define behavior, adapters define delivery. This package only owns the behavior layer.
git clone https://github.com/phcdevworks/spectre-components.git
cd spectre-components
npm install
npm run check # full release validation gateRequires Node.js ^22.13.0 || >=24.0.0 and npm 12.0.2.
| Command | Purpose |
|---|---|
npm run check |
Full validation (lint → typecheck → test → build → export, contract, invariant, and ecosystem checks) |
npm run build |
Compile ESM + CJS with declarations into dist/ |
npm test |
Run Vitest suite under happy-dom |
npm run test:browser |
Run native browser behavior regressions (also run in CI) |
npm run test:visual |
Run opt-in screenshot regressions |
npm run lint |
ESLint |
npm run check:exports |
Verify packed ESM/CommonJS entry points and declaration targets |
npm run check:contract |
Verify built exports match components.contract.json |
npm run check:invariants |
Verify light-DOM and no-hardcoded-visual invariants |
npm run check:ecosystem |
Validate spectre.manifest.json |
npm run dev |
tsup watch mode |
npm run clean |
Remove dist/ and coverage/ |
Key source areas:
src/components/— one directory per custom elementsrc/utils/—base.ts,projectable.ts,form.ts,dom.tssrc/index.ts— root public API and bulk registration helpertests/— component behavior coverage (Vitest + happy-dom)scripts/check-exports.ts— post-build export resolution check
Build fails with type errors — TypeScript 6 is required. Run npm install,
then npm run build.
Tests fail in CI but pass locally — Tests run under happy-dom. Confirm you
are on Node ^22.13.0 || >=24.0.0. CI tests both versions.
Custom element already defined — Each defineSpectre*() helper is
idempotent; calling it twice is safe. If you see conflicts, two different
versions of this package may be loaded in the same page.
Styles are not applying — The Spectre CSS layers must load before components
are registered. Import @phcdevworks/spectre-tokens/index.css and
@phcdevworks/spectre-ui/index.css at the top of your entry module.
Properties not reflecting in React 18 — React 18 sets custom element
properties as attributes. Use a ref to set properties imperatively, or upgrade
to React 19 which supports custom elements fully.
Run the full validation gate before any pull request:
npm run checkThis runs: lint → typecheck → tests → build → export validation → contract validation → invariant checks → ecosystem manifest validation. 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
dependency updates. GitHub Copilot provides development support.
All AI agents with repository access (Claude Code, Codex, Copilot, Jules) have commit, push, and tag authority in this repository. Publishing to npm remains Bradley Potts's sole authority. See AGENTS.md for the full commit-policy and release-authority grant.
Protected from automated change: component public API surface (tags, properties, events, slots, ARIA), the light-DOM rendering model, and the zero-hardcode-values rule. See AGENTS.md for full agent governance and boundary rules.
PHCDevworks maintains this package as part of the Spectre suite.
Contribution boundaries:
- Components must consume
@phcdevworks/spectre-uiclass helpers — do not recreate CSS locally. - Design values must come from
@phcdevworks/spectre-tokens— do not hardcode colors, spacing, or other visual primitives. - Component tags, properties, events, slots, and ARIA behavior are stable API — breaking changes require a semver major bump.
- Render in light DOM only — Shadow DOM changes require design-system approval.
- No framework-specific code — no JSX, SFCs, or Astro components in this package.
- Run
npm run checkbefore opening a pull request.
See CONTRIBUTING.md for the full guide.
MIT © PHCDevworks. See LICENSE.