@phcdevworks/spectre-components 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +504 -202
  2. package/dist/alert.cjs +455 -0
  3. package/dist/alert.cjs.map +1 -0
  4. package/dist/alert.d.cts +68 -0
  5. package/dist/alert.d.ts +68 -0
  6. package/dist/alert.js +450 -0
  7. package/dist/alert.js.map +1 -0
  8. package/dist/avatar.cjs +454 -0
  9. package/dist/avatar.cjs.map +1 -0
  10. package/dist/avatar.d.cts +74 -0
  11. package/dist/avatar.d.ts +74 -0
  12. package/dist/avatar.js +449 -0
  13. package/dist/avatar.js.map +1 -0
  14. package/dist/badge.cjs +454 -0
  15. package/dist/badge.cjs.map +1 -0
  16. package/dist/badge.d.cts +62 -0
  17. package/dist/badge.d.ts +62 -0
  18. package/dist/badge.js +449 -0
  19. package/dist/badge.js.map +1 -0
  20. package/dist/button.cjs.map +1 -1
  21. package/dist/button.d.cts +2 -2
  22. package/dist/button.d.ts +2 -2
  23. package/dist/button.js.map +1 -1
  24. package/dist/card.cjs +448 -0
  25. package/dist/card.cjs.map +1 -0
  26. package/dist/card.d.cts +68 -0
  27. package/dist/card.d.ts +68 -0
  28. package/dist/card.js +444 -0
  29. package/dist/card.js.map +1 -0
  30. package/dist/fieldset.cjs +14 -0
  31. package/dist/fieldset.cjs.map +1 -1
  32. package/dist/fieldset.d.cts +6 -0
  33. package/dist/fieldset.d.ts +6 -0
  34. package/dist/fieldset.js +14 -0
  35. package/dist/fieldset.js.map +1 -1
  36. package/dist/form-BumNzIay.d.cts +28 -0
  37. package/dist/form-BumNzIay.d.ts +28 -0
  38. package/dist/icon-box.cjs +466 -0
  39. package/dist/icon-box.cjs.map +1 -0
  40. package/dist/icon-box.d.cts +74 -0
  41. package/dist/icon-box.d.ts +74 -0
  42. package/dist/icon-box.js +461 -0
  43. package/dist/icon-box.js.map +1 -0
  44. package/dist/index.cjs +1123 -0
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +11 -1
  47. package/dist/index.d.ts +11 -1
  48. package/dist/index.js +1090 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/input.cjs.map +1 -1
  51. package/dist/input.d.cts +2 -2
  52. package/dist/input.d.ts +2 -2
  53. package/dist/input.js.map +1 -1
  54. package/dist/pricing-card.cjs +432 -0
  55. package/dist/pricing-card.cjs.map +1 -0
  56. package/dist/pricing-card.d.cts +60 -0
  57. package/dist/pricing-card.d.ts +60 -0
  58. package/dist/pricing-card.js +429 -0
  59. package/dist/pricing-card.js.map +1 -0
  60. package/dist/rating.cjs +377 -0
  61. package/dist/rating.cjs.map +1 -0
  62. package/dist/rating.d.cts +68 -0
  63. package/dist/rating.d.ts +68 -0
  64. package/dist/rating.js +373 -0
  65. package/dist/rating.js.map +1 -0
  66. package/dist/select.cjs +14 -0
  67. package/dist/select.cjs.map +1 -1
  68. package/dist/select.d.cts +7 -1
  69. package/dist/select.d.ts +7 -1
  70. package/dist/select.js +14 -0
  71. package/dist/select.js.map +1 -1
  72. package/dist/spinner.cjs +356 -0
  73. package/dist/spinner.cjs.map +1 -0
  74. package/dist/spinner.d.cts +52 -0
  75. package/dist/spinner.d.ts +52 -0
  76. package/dist/spinner.js +351 -0
  77. package/dist/spinner.js.map +1 -0
  78. package/dist/tag.cjs +473 -0
  79. package/dist/tag.cjs.map +1 -0
  80. package/dist/tag.d.cts +80 -0
  81. package/dist/tag.d.ts +80 -0
  82. package/dist/tag.js +468 -0
  83. package/dist/tag.js.map +1 -0
  84. package/dist/testimonial.cjs +444 -0
  85. package/dist/testimonial.cjs.map +1 -0
  86. package/dist/testimonial.d.cts +62 -0
  87. package/dist/testimonial.d.ts +62 -0
  88. package/dist/testimonial.js +440 -0
  89. package/dist/testimonial.js.map +1 -0
  90. package/dist/textarea.cjs.map +1 -1
  91. package/dist/textarea.d.cts +1 -1
  92. package/dist/textarea.d.ts +1 -1
  93. package/dist/textarea.js.map +1 -1
  94. package/package.json +68 -13
  95. package/dist/form-yR_u97J-.d.cts +0 -10
  96. package/dist/form-yR_u97J-.d.ts +0 -10
package/README.md CHANGED
@@ -8,9 +8,9 @@
8
8
  `@phcdevworks/spectre-components` is the **Layer 3** Lit-based web component
9
9
  package of the Spectre design system. It turns Spectre tokens
10
10
  (`@phcdevworks/spectre-tokens`) and Spectre UI styling contracts
11
- (`@phcdevworks/spectre-ui`) into reusable, accessible, framework-agnostic
12
- custom elements — the canonical component implementation layer for Spectre,
13
- designed to be consumed directly or wrapped by downstream adapter packages.
11
+ (`@phcdevworks/spectre-ui`) into reusable, accessible, framework-agnostic custom
12
+ elements — the canonical component implementation layer for Spectre, designed to
13
+ be consumed directly or wrapped by downstream adapter packages.
14
14
 
15
15
  [Contributing](CONTRIBUTING.md) | [Code of Conduct](CODE_OF_CONDUCT.md) |
16
16
  [Changelog](CHANGELOG.md) | [Roadmap](ROADMAP.md) |
@@ -22,23 +22,23 @@ designed to be consumed directly or wrapped by downstream adapter packages.
22
22
  styling contract that maps Spectre tokens to visual output. It ships CSS rules
23
23
  and JavaScript class-name helpers — nothing more.
24
24
 
25
- This package sits above that. It owns **behavior**: the Lit element classes
26
- that apply those CSS recipes, forward ARIA attributes to native elements,
27
- manage focus delegation, handle content projection, validate properties, and
28
- expose a stable TypeScript API surface for downstream adapters.
25
+ This package sits above that. It owns **behavior**: the Lit element classes that
26
+ apply those CSS recipes, forward ARIA attributes to native elements, manage
27
+ focus delegation, handle content projection, validate properties, and expose a
28
+ stable TypeScript API surface for downstream adapters.
29
29
 
30
30
  The separation keeps each layer focused:
31
31
 
32
- | Layer | Package | Owns |
33
- |-------|---------|------|
34
- | L1 | `@phcdevworks/spectre-tokens` | Design values and semantic meaning |
35
- | L2 | `@phcdevworks/spectre-ui` | CSS recipes and styling contracts |
32
+ | Layer | Package | Owns |
33
+ | ------ | ------------------------------------- | -------------------------------------- |
34
+ | L1 | `@phcdevworks/spectre-tokens` | Design values and semantic meaning |
35
+ | L2 | `@phcdevworks/spectre-ui` | CSS recipes and styling contracts |
36
36
  | **L3** | **`@phcdevworks/spectre-components`** | **Lit web component behavior and API** |
37
- | L4 | Downstream adapters | Framework-specific delivery |
37
+ | L4 | Downstream adapters | Framework-specific delivery |
38
38
 
39
- If you only need CSS class names, use `@phcdevworks/spectre-ui` directly. If
40
- you need ready-to-use HTML elements with behavior, accessibility, and a typed
41
- API, use this package.
39
+ If you only need CSS class names, use `@phcdevworks/spectre-ui` directly. If you
40
+ need ready-to-use HTML elements with behavior, accessibility, and a typed API,
41
+ use this package.
42
42
 
43
43
  ## Key capabilities
44
44
 
@@ -57,16 +57,17 @@ API, use this package.
57
57
 
58
58
  - You are building UI with the Spectre design system and want standards-based
59
59
  custom elements with baked-in behavior and accessibility.
60
- - You want typed form controls (`sp-button`, `sp-input`, `sp-select`, etc.)
61
- that work in any framework or in plain HTML.
60
+ - You want typed form controls (`sp-button`, `sp-input`, `sp-select`, etc.) and
61
+ display primitives (`sp-badge`, `sp-card`, `sp-rating`, etc.) that work in any
62
+ framework or in plain HTML.
62
63
  - You are writing a framework adapter (React, Vue, Astro) and need a reliable,
63
64
  stable element layer to wrap.
64
65
 
65
66
  ## When not to use this package
66
67
 
67
68
  - You only need CSS class names — use `@phcdevworks/spectre-ui` directly.
68
- - You are adding routing, shell logic, or app-startup orchestration — those
69
- are out of scope here.
69
+ - You are adding routing, shell logic, or app-startup orchestration — those are
70
+ out of scope here.
70
71
  - You need framework-specific component files (JSX, SFCs, Astro components) —
71
72
  those belong in a downstream adapter package.
72
73
 
@@ -89,19 +90,30 @@ consumption.
89
90
  <html lang="en">
90
91
  <head>
91
92
  <!-- Spectre CSS layers must load before any markup is rendered -->
92
- <link rel="stylesheet" href="/node_modules/@phcdevworks/spectre-tokens/index.css" />
93
- <link rel="stylesheet" href="/node_modules/@phcdevworks/spectre-ui/index.css" />
93
+ <link
94
+ rel="stylesheet"
95
+ href="/node_modules/@phcdevworks/spectre-tokens/index.css"
96
+ />
97
+ <link
98
+ rel="stylesheet"
99
+ href="/node_modules/@phcdevworks/spectre-ui/index.css"
100
+ />
94
101
  </head>
95
102
  <body>
96
103
  <sp-label for="email">Email address</sp-label>
97
- <sp-input id="email" name="email" type="email" placeholder="you@example.com"></sp-input>
104
+ <sp-input
105
+ id="email"
106
+ name="email"
107
+ type="email"
108
+ placeholder="you@example.com"
109
+ ></sp-input>
98
110
 
99
111
  <sp-button variant="primary" type="submit">Send</sp-button>
100
112
  <sp-button variant="ghost" type="button">Cancel</sp-button>
101
113
 
102
114
  <script type="module">
103
- import { defineSpectreComponents } from '/node_modules/@phcdevworks/spectre-components/dist/index.js';
104
- defineSpectreComponents();
115
+ import { defineSpectreComponents } from '/node_modules/@phcdevworks/spectre-components/dist/index.js'
116
+ defineSpectreComponents()
105
117
  </script>
106
118
  </body>
107
119
  </html>
@@ -110,18 +122,18 @@ consumption.
110
122
  ### JavaScript / TypeScript module
111
123
 
112
124
  ```ts
113
- import '@phcdevworks/spectre-tokens/index.css';
114
- import '@phcdevworks/spectre-ui/index.css';
125
+ import '@phcdevworks/spectre-tokens/index.css'
126
+ import '@phcdevworks/spectre-ui/index.css'
115
127
 
116
128
  // Register everything at once
117
- import { defineSpectreComponents } from '@phcdevworks/spectre-components';
118
- defineSpectreComponents();
129
+ import { defineSpectreComponents } from '@phcdevworks/spectre-components'
130
+ defineSpectreComponents()
119
131
 
120
132
  // Or register only what you use
121
- import { defineSpectreButton } from '@phcdevworks/spectre-components/button';
122
- import { defineSpectreInput } from '@phcdevworks/spectre-components/input';
123
- defineSpectreButton();
124
- defineSpectreInput();
133
+ import { defineSpectreButton } from '@phcdevworks/spectre-components/button'
134
+ import { defineSpectreInput } from '@phcdevworks/spectre-components/input'
135
+ defineSpectreButton()
136
+ defineSpectreInput()
125
137
  ```
126
138
 
127
139
  ### Full form example
@@ -154,10 +166,11 @@ defineSpectreInput();
154
166
 
155
167
  ### Framework integration note
156
168
 
157
- These are standard HTML custom elements. They work in every major framework
158
- that supports the Custom Elements standard:
169
+ These are standard HTML custom elements. They work in every major framework that
170
+ supports the Custom Elements standard:
159
171
 
160
172
  **React 19+** — supports custom element properties and events natively:
173
+
161
174
  ```tsx
162
175
  // React 19: properties and events work directly
163
176
  <sp-input name="email" type="email" onInput={(e) => setValue(e.target.value)} />
@@ -165,27 +178,38 @@ that supports the Custom Elements standard:
165
178
 
166
179
  **React 18 and below** — set attributes via `ref` for properties, listen for
167
180
  native events on the element:
181
+
168
182
  ```tsx
169
- const inputRef = useRef(null);
183
+ const inputRef = useRef(null)
170
184
  useEffect(() => {
171
- if (inputRef.current) inputRef.current.invalid = true;
172
- }, []);
173
- <sp-input ref={inputRef} name="email" />
185
+ if (inputRef.current) inputRef.current.invalid = true
186
+ }, [])
187
+
188
+ return <sp-input ref={inputRef} name="email" />
174
189
  ```
175
190
 
176
- **Vue 3** — supports custom elements out of the box with `v-bind` and
177
- `v-on` directive compatibility. Mark the `sp-*` prefix in `compilerOptions`
178
- as a custom element to suppress unknown-element warnings:
191
+ **Vue 3** — supports custom elements out of the box with `v-bind` and `v-on`
192
+ directive compatibility. Mark the `sp-*` prefix in `compilerOptions` as a custom
193
+ element to suppress unknown-element warnings:
194
+
179
195
  ```ts
180
196
  // vite.config.ts
181
- plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('sp-') } } })]
197
+ plugins: [
198
+ vue({
199
+ template: {
200
+ compilerOptions: { isCustomElement: (tag) => tag.startsWith('sp-') }
201
+ }
202
+ })
203
+ ]
182
204
  ```
205
+
183
206
  ```html
184
207
  <sp-input name="email" :invalid="hasError" @change="handleChange" />
185
208
  ```
186
209
 
187
- **Astro** — use components as static custom elements or with `client:load`
188
- when JavaScript interactivity is needed:
210
+ **Astro** — use components as static custom elements or with `client:load` when
211
+ JavaScript interactivity is needed:
212
+
189
213
  ```astro
190
214
  ---
191
215
  import '@phcdevworks/spectre-tokens/index.css';
@@ -210,25 +234,25 @@ All components follow WCAG 2.1 AA baseline expectations by default.
210
234
  inner native element so screen readers receive them on the correct target.
211
235
 
212
236
  **Native element semantics** — every component renders a real native element
213
- (`<button>`, `<input>`, `<textarea>`, `<select>`, `<label>`, `<fieldset>`)
214
- so browser accessibility APIs work without customization.
237
+ (`<button>`, `<input>`, `<textarea>`, `<select>`, `<label>`, `<fieldset>`) or a
238
+ semantic light-DOM container with forwarded ARIA attributes, so browser
239
+ accessibility APIs work without customization.
215
240
 
216
241
  **State communication**
217
242
 
218
- | State | ARIA effect |
219
- |-------|-------------|
220
- | `loading` | `aria-busy="true"` on the native element |
221
- | `invalid` | `aria-invalid="true"` on the native element |
243
+ | State | ARIA effect |
244
+ | ---------- | ---------------------------------------------------- |
245
+ | `loading` | `aria-busy="true"` on the native element |
246
+ | `invalid` | `aria-invalid="true"` on the native element |
222
247
  | `disabled` | native `disabled` attribute (removes from tab order) |
223
- | `required` | native `required` attribute |
248
+ | `required` | native `required` attribute |
224
249
 
225
- **Focus delegation** — `.focus()` and `.blur()` called on the host are
226
- delegated to the inner native element so external `focus()` calls work as
227
- expected.
250
+ **Focus delegation** — `.focus()` and `.blur()` called on the host are delegated
251
+ to the inner native element so external `focus()` calls work as expected.
228
252
 
229
- **Label association** — use `<sp-label for="id">` paired with `id` on the
230
- target control, or wrap controls inside a `<sp-fieldset>`. The `for` attribute
231
- forwards to the native `<label>` element.
253
+ **Label association** — use `<sp-label for="id">` paired with `id` on the target
254
+ control, or wrap controls inside a `<sp-fieldset>`. The `for` attribute forwards
255
+ to the native `<label>` element.
232
256
 
233
257
  **Keyboard behavior** — provided entirely by the native element inside each
234
258
  component. No custom keyboard handling is layered on top.
@@ -236,19 +260,23 @@ component. No custom keyboard handling is layered on top.
236
260
  ## Light DOM rendering
237
261
 
238
262
  All components render in **light DOM** (`createRenderRoot() { return this; }`).
239
- This is intentional: it allows `@phcdevworks/spectre-ui` global CSS to reach
240
- the native element directly without Shadow DOM piercing.
263
+ This is intentional: it allows `@phcdevworks/spectre-ui` global CSS to reach the
264
+ native element directly without Shadow DOM piercing.
241
265
 
242
- As a result, these components have no `::part()` exports — the native element
243
- is directly selectable using standard CSS combinators or the stable internal
244
- data attributes:
266
+ As a result, these components have no `::part()` exports — the native element is
267
+ directly selectable using standard CSS combinators or the stable internal data
268
+ attributes:
245
269
 
246
270
  ```css
247
271
  /* Target the native input inside sp-input */
248
- sp-input input { font-size: 0.875rem; }
272
+ sp-input input {
273
+ font-size: 0.875rem;
274
+ }
249
275
 
250
276
  /* Stable internal hook — won't break if markup restructures */
251
- sp-input [data-sp-input-native] { font-size: 0.875rem; }
277
+ sp-input [data-sp-input-native] {
278
+ font-size: 0.875rem;
279
+ }
252
280
  ```
253
281
 
254
282
  Do not switch any component from light DOM to Shadow DOM without a
@@ -262,26 +290,26 @@ Renders a `<button>` with Spectre variant, size, loading, and pill support.
262
290
 
263
291
  **Attributes**
264
292
 
265
- | Attribute | Type | Default | Description |
266
- |-----------|------|---------|-------------|
267
- | `variant` | `primary \| secondary \| ghost \| danger \| success \| cta \| accent` | `primary` | Visual style |
268
- | `size` | `sm \| md \| lg` | `md` | Control size |
269
- | `type` | `button \| submit \| reset` | `button` | Native button type |
270
- | `label` | string | — | Text label (overridden by content projection) |
271
- | `loading` | boolean | `false` | Busy state — disables the button and shows loading label |
272
- | `loading-label` | string | `Loading` | Accessible text shown during loading |
273
- | `disabled` | boolean | `false` | Disables the button |
274
- | `full-width` | boolean | `false` | Spans full container width |
275
- | `pill` | boolean | `false` | Pill / fully-rounded corners |
276
- | `name` | string | — | Form field name |
277
- | `value` | string | `''` | Submitted value |
278
- | `form` | string | — | Associates with a form by ID |
279
- | `autofocus` | boolean | `false` | Autofocus on page load |
280
- | `id` | string | — | Forwarded to the native `<button>` |
281
- | `title` | string | — | Forwarded to the native `<button>` |
282
- | `aria-label` | string | — | Forwarded to the native `<button>` |
283
- | `aria-labelledby` | string | — | Forwarded to the native `<button>` |
284
- | `aria-describedby` | string | — | Forwarded to the native `<button>` |
293
+ | Attribute | Type | Default | Description |
294
+ | ------------------ | --------------------------------------------------------------------- | --------- | -------------------------------------------------------- |
295
+ | `variant` | `primary \| secondary \| ghost \| danger \| success \| cta \| accent` | `primary` | Visual style |
296
+ | `size` | `sm \| md \| lg` | `md` | Control size |
297
+ | `type` | `button \| submit \| reset` | `button` | Native button type |
298
+ | `label` | string | — | Text label (overridden by content projection) |
299
+ | `loading` | boolean | `false` | Busy state — disables the button and shows loading label |
300
+ | `loading-label` | string | `Loading` | Accessible text shown during loading |
301
+ | `disabled` | boolean | `false` | Disables the button |
302
+ | `full-width` | boolean | `false` | Spans full container width |
303
+ | `pill` | boolean | `false` | Pill / fully-rounded corners |
304
+ | `name` | string | — | Form field name |
305
+ | `value` | string | `''` | Submitted value |
306
+ | `form` | string | — | Associates with a form by ID |
307
+ | `autofocus` | boolean | `false` | Autofocus on page load |
308
+ | `id` | string | — | Forwarded to the native `<button>` |
309
+ | `title` | string | — | Forwarded to the native `<button>` |
310
+ | `aria-label` | string | — | Forwarded to the native `<button>` |
311
+ | `aria-labelledby` | string | — | Forwarded to the native `<button>` |
312
+ | `aria-describedby` | string | — | Forwarded to the native `<button>` |
285
313
 
286
314
  **Events** — native button events bubble normally (`click`, `focus`, `blur`).
287
315
 
@@ -305,28 +333,28 @@ Renders an `<input>` with state, size, and type support.
305
333
 
306
334
  **Attributes**
307
335
 
308
- | Attribute | Type | Default | Description |
309
- |-----------|------|---------|-------------|
310
- | `type` | `text \| email \| password \| search \| tel \| url \| number \| date \| datetime-local \| month \| time \| week` | `text` | Native input type |
311
- | `size` | `sm \| md \| lg` | `md` | Control size |
312
- | `name` | string | — | Form field name |
313
- | `value` | string | `''` | Current value |
314
- | `placeholder` | string | — | Placeholder text |
315
- | `disabled` | boolean | `false` | Disables the input |
316
- | `loading` | boolean | `false` | Busy state |
317
- | `readonly` | boolean | `false` | Read-only mode |
318
- | `required` | boolean | `false` | Marks field as required |
319
- | `invalid` | boolean | `false` | Error state (`aria-invalid`) |
320
- | `success` | boolean | `false` | Success state |
321
- | `full-width` | boolean | `false` | Spans full container width |
322
- | `pill` | boolean | `false` | Pill / fully-rounded corners |
323
- | `autocomplete` | string | — | Native autocomplete hint |
324
- | `inputmode` | string | — | Virtual keyboard hint |
325
- | `min` / `max` / `step` | string | — | Numeric/date range |
326
- | `minlength` / `maxlength` | number | — | Character length constraints |
327
- | `form` | string | — | Associates with a form by ID |
328
- | `autofocus` | boolean | `false` | Autofocus on page load |
329
- | `id` / `title` / `aria-*` | string | — | Forwarded to native `<input>` |
336
+ | Attribute | Type | Default | Description |
337
+ | ------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ----------------------------- |
338
+ | `type` | `text \| email \| password \| search \| tel \| url \| number \| date \| datetime-local \| month \| time \| week` | `text` | Native input type |
339
+ | `size` | `sm \| md \| lg` | `md` | Control size |
340
+ | `name` | string | — | Form field name |
341
+ | `value` | string | `''` | Current value |
342
+ | `placeholder` | string | — | Placeholder text |
343
+ | `disabled` | boolean | `false` | Disables the input |
344
+ | `loading` | boolean | `false` | Busy state |
345
+ | `readonly` | boolean | `false` | Read-only mode |
346
+ | `required` | boolean | `false` | Marks field as required |
347
+ | `invalid` | boolean | `false` | Error state (`aria-invalid`) |
348
+ | `success` | boolean | `false` | Success state |
349
+ | `full-width` | boolean | `false` | Spans full container width |
350
+ | `pill` | boolean | `false` | Pill / fully-rounded corners |
351
+ | `autocomplete` | string | — | Native autocomplete hint |
352
+ | `inputmode` | string | — | Virtual keyboard hint |
353
+ | `min` / `max` / `step` | string | — | Numeric/date range |
354
+ | `minlength` / `maxlength` | number | — | Character length constraints |
355
+ | `form` | string | — | Associates with a form by ID |
356
+ | `autofocus` | boolean | `false` | Autofocus on page load |
357
+ | `id` / `title` / `aria-*` | string | — | Forwarded to native `<input>` |
330
358
 
331
359
  **Events** — `input` and `change` fire from the native `<input>` and bubble.
332
360
 
@@ -338,30 +366,32 @@ Renders an `<input>` with state, size, and type support.
338
366
 
339
367
  Renders a `<textarea>` with row control and resize support.
340
368
 
341
- **Attributes** — same as `sp-input` except no `type`, `min`, `max`, `step`, and adds:
369
+ **Attributes** — same as `sp-input` except no `type`, `min`, `max`, `step`, and
370
+ adds:
342
371
 
343
- | Attribute | Type | Default | Description |
344
- |-----------|------|---------|-------------|
345
- | `rows` | number | `2` | Visible row height |
372
+ | Attribute | Type | Default | Description |
373
+ | --------- | ------ | ------- | ------------------ |
374
+ | `rows` | number | `2` | Visible row height |
346
375
 
347
376
  **Events** — `input` and `change` fire from the native `<textarea>`.
348
377
 
349
- **Internal target** — `[data-sp-textarea-native]` selects the native `<textarea>`.
378
+ **Internal target** — `[data-sp-textarea-native]` selects the native
379
+ `<textarea>`.
350
380
 
351
381
  ---
352
382
 
353
383
  ### sp-select
354
384
 
355
- Renders a `<select>`. Pass `<option>` elements as children — they are
356
- projected into the native select element.
385
+ Renders a `<select>`. Pass `<option>` elements as children — they are projected
386
+ into the native select element.
357
387
 
358
388
  **Attributes** — same as `sp-input` minus `type`, `placeholder`, `readonly`,
359
389
  `inputmode`, `min`, `max`, `step`, `minlength`, `maxlength`.
360
390
 
361
391
  **Events** — `input` and `change` fire from the native `<select>`.
362
392
 
363
- **Content projection** — `<option>` and `<optgroup>` children are moved into
364
- the native `<select>`:
393
+ **Content projection** — `<option>` and `<optgroup>` children are moved into the
394
+ native `<select>`:
365
395
 
366
396
  ```html
367
397
  <sp-select name="country" required>
@@ -383,18 +413,18 @@ Renders a `<label>` wrapping an `<input type="checkbox">` with indicator.
383
413
 
384
414
  **Attributes**
385
415
 
386
- | Attribute | Type | Default | Description |
387
- |-----------|------|---------|-------------|
388
- | `name` | string | — | Form field name |
389
- | `value` | string | `on` | Submitted value when checked |
390
- | `checked` | boolean | `false` | Checked state |
391
- | `label` | string | — | Text label (overridden by content projection) |
392
- | `disabled` | boolean | `false` | Disables the checkbox |
393
- | `loading` | boolean | `false` | Busy state |
394
- | `required` | boolean | `false` | Marks field as required |
395
- | `invalid` | boolean | `false` | Error state |
396
- | `success` | boolean | `false` | Success state |
397
- | `form` / `autofocus` / `id` / `title` / `aria-*` | — | — | Forwarded to native `<input>` |
416
+ | Attribute | Type | Default | Description |
417
+ | ------------------------------------------------ | ------- | ------- | --------------------------------------------- |
418
+ | `name` | string | — | Form field name |
419
+ | `value` | string | `on` | Submitted value when checked |
420
+ | `checked` | boolean | `false` | Checked state |
421
+ | `label` | string | — | Text label (overridden by content projection) |
422
+ | `disabled` | boolean | `false` | Disables the checkbox |
423
+ | `loading` | boolean | `false` | Busy state |
424
+ | `required` | boolean | `false` | Marks field as required |
425
+ | `invalid` | boolean | `false` | Error state |
426
+ | `success` | boolean | `false` | Success state |
427
+ | `form` / `autofocus` / `id` / `title` / `aria-*` | — | — | Forwarded to native `<input>` |
398
428
 
399
429
  **Events** — `input` and `change` fire from the native checkbox input.
400
430
 
@@ -413,8 +443,8 @@ markup):
413
443
 
414
444
  ### sp-radio
415
445
 
416
- Renders a `<label>` wrapping an `<input type="radio">` with indicator.
417
- Group multiple `sp-radio` elements by giving them the same `name`.
446
+ Renders a `<label>` wrapping an `<input type="radio">` with indicator. Group
447
+ multiple `sp-radio` elements by giving them the same `name`.
418
448
 
419
449
  **Attributes** — same as `sp-checkbox`. `value` defaults to `on`.
420
450
 
@@ -433,15 +463,15 @@ Group multiple `sp-radio` elements by giving them the same `name`.
433
463
 
434
464
  ### sp-label
435
465
 
436
- Renders a `<label>` with `for` forwarding. Use to associate a visible label
437
- with any form control.
466
+ Renders a `<label>` with `for` forwarding. Use to associate a visible label with
467
+ any form control.
438
468
 
439
469
  **Attributes**
440
470
 
441
- | Attribute | Type | Default | Description |
442
- |-----------|------|---------|-------------|
443
- | `for` | string | — | ID of the associated control (forwarded to native `<label>`) |
444
- | `id` / `title` / `aria-*` | string | — | Forwarded to native `<label>` |
471
+ | Attribute | Type | Default | Description |
472
+ | ------------------------- | ------ | ------- | ------------------------------------------------------------ |
473
+ | `for` | string | — | ID of the associated control (forwarded to native `<label>`) |
474
+ | `id` / `title` / `aria-*` | string | — | Forwarded to native `<label>` |
445
475
 
446
476
  **Content projection** — children become the label text (supports rich markup):
447
477
 
@@ -461,14 +491,14 @@ Renders a `<fieldset>` with optional legend and group-level state.
461
491
 
462
492
  **Attributes**
463
493
 
464
- | Attribute | Type | Default | Description |
465
- |-----------|------|---------|-------------|
466
- | `legend` | string | — | Text for the `<legend>` element |
467
- | `disabled` | boolean | `false` | Disables all controls in the group |
468
- | `loading` | boolean | `false` | Busy state |
469
- | `invalid` | boolean | `false` | Group-level error state |
470
- | `success` | boolean | `false` | Group-level success state |
471
- | `form` / `name` / `id` / `title` / `aria-*` | string | — | Forwarded to native `<fieldset>` |
494
+ | Attribute | Type | Default | Description |
495
+ | ------------------------------------------- | ------- | ------- | ---------------------------------- |
496
+ | `legend` | string | — | Text for the `<legend>` element |
497
+ | `disabled` | boolean | `false` | Disables all controls in the group |
498
+ | `loading` | boolean | `false` | Busy state |
499
+ | `invalid` | boolean | `false` | Group-level error state |
500
+ | `success` | boolean | `false` | Group-level success state |
501
+ | `form` / `name` / `id` / `title` / `aria-*` | string | — | Forwarded to native `<fieldset>` |
472
502
 
473
503
  **Content projection** — children are placed inside the native `<fieldset>`
474
504
  alongside the legend:
@@ -483,7 +513,246 @@ alongside the legend:
483
513
  </sp-fieldset>
484
514
  ```
485
515
 
486
- **Internal target** — `[data-sp-fieldset-native]` selects the native `<fieldset>`.
516
+ **Internal target** — `[data-sp-fieldset-native]` selects the native
517
+ `<fieldset>`.
518
+
519
+ ---
520
+
521
+ ### sp-badge
522
+
523
+ Renders a `<span>` display primitive backed by the Spectre badge recipe.
524
+
525
+ **Attributes**
526
+
527
+ | Attribute | Type | Default | Description |
528
+ | ------------------------- | -------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------- |
529
+ | `variant` | `primary \| secondary \| ghost \| danger \| success \| warning \| info \| accent \| cta \| neutral \| outline` | `primary` | Visual style |
530
+ | `size` | `sm \| md \| lg` | `md` | Badge size |
531
+ | `disabled` | boolean | `false` | Disabled visual state |
532
+ | `loading` | boolean | `false` | Busy visual state |
533
+ | `full-width` | boolean | `false` | Spans full container width |
534
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<span>` |
535
+
536
+ **Content projection** — children become the badge content.
537
+
538
+ **Internal target** — `[data-sp-badge-native]` selects the native `<span>`.
539
+
540
+ ---
541
+
542
+ ### sp-card
543
+
544
+ Renders a `<div>` container backed by the Spectre card recipe.
545
+
546
+ **Attributes**
547
+
548
+ | Attribute | Type | Default | Description |
549
+ | ------------------------- | -------------------------------------- | ---------- | ------------------------------- |
550
+ | `variant` | `elevated \| flat \| outline \| ghost` | `elevated` | Visual style |
551
+ | `padded` | boolean | `true` | Applies card padding |
552
+ | `full-height` | boolean | `false` | Spans full container height |
553
+ | `interactive` | boolean | `false` | Applies interactive styling |
554
+ | `disabled` | boolean | `false` | Disabled visual state |
555
+ | `loading` | boolean | `false` | Busy visual state |
556
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
557
+
558
+ **Content projection** — children become the card content.
559
+
560
+ **Internal target** — `[data-sp-card-native]` selects the native `<div>`.
561
+
562
+ ---
563
+
564
+ ### sp-icon-box
565
+
566
+ Renders a `<div>` icon container backed by the Spectre icon-box recipe.
567
+
568
+ **Attributes**
569
+
570
+ | Attribute | Type | Default | Description |
571
+ | ------------------------- | -------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------- |
572
+ | `variant` | `primary \| secondary \| ghost \| danger \| success \| warning \| info \| accent \| cta \| neutral \| outline` | `primary` | Visual style |
573
+ | `size` | `sm \| md \| lg` | `md` | Icon-box size |
574
+ | `disabled` | boolean | `false` | Disabled visual state |
575
+ | `loading` | boolean | `false` | Busy visual state |
576
+ | `interactive` | boolean | `false` | Applies interactive styling |
577
+ | `pill` | boolean | `false` | Pill / fully-rounded corners |
578
+ | `full-width` | boolean | `false` | Spans full container width |
579
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
580
+
581
+ **Content projection** — children become the icon-box content.
582
+
583
+ **Internal target** — `[data-sp-icon-box-native]` selects the native `<div>`.
584
+
585
+ ---
586
+
587
+ ### sp-rating
588
+
589
+ Renders a read-only rating visualization with generated star spans.
590
+
591
+ **Attributes**
592
+
593
+ | Attribute | Type | Default | Description |
594
+ | ------------------------- | ---------------- | ------- | -------------------------------------- |
595
+ | `value` | number | `0` | Filled star count |
596
+ | `max` | number | `5` | Total star count |
597
+ | `size` | `sm \| md \| lg` | `md` | Rating size |
598
+ | `label` | string | — | Optional visible text beside the stars |
599
+ | `disabled` | boolean | `false` | Disabled visual state |
600
+ | `loading` | boolean | `false` | Busy visual state |
601
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the rating container |
602
+
603
+ **Accessibility** — renders `role="img"` and computes an accessible label like
604
+ `Rating: 4 out of 5` unless `aria-label` is provided.
605
+
606
+ **Internal target** — `[data-sp-rating-native]` selects the rating container.
607
+
608
+ ---
609
+
610
+ ### sp-testimonial
611
+
612
+ Renders a `<div>` testimonial container backed by the Spectre testimonial
613
+ recipe.
614
+
615
+ **Attributes**
616
+
617
+ | Attribute | Type | Default | Description |
618
+ | ------------------------- | -------------------------------------- | ---------- | ------------------------------- |
619
+ | `variant` | `elevated \| flat \| outline \| ghost` | `elevated` | Visual style |
620
+ | `full-height` | boolean | `false` | Spans full container height |
621
+ | `interactive` | boolean | `false` | Applies interactive styling |
622
+ | `disabled` | boolean | `false` | Disabled visual state |
623
+ | `loading` | boolean | `false` | Busy visual state |
624
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
625
+
626
+ **Content projection** — children become the testimonial content.
627
+
628
+ **Internal target** — `[data-sp-testimonial-native]` selects the native `<div>`.
629
+
630
+ ---
631
+
632
+ ### sp-alert
633
+
634
+ Renders a `<div role="alert">` display primitive backed by the Spectre alert
635
+ recipe.
636
+
637
+ **Attributes**
638
+
639
+ | Attribute | Type | Default | Description |
640
+ | ------------------------- | ------------------------------------------------- | ------- | ------------------------------- |
641
+ | `variant` | `info \| success \| warning \| danger \| neutral` | `info` | Visual style |
642
+ | `size` | `sm \| md \| lg` | `md` | Alert size |
643
+ | `dismissed` | boolean | `false` | Dismissed visual state |
644
+ | `disabled` | boolean | `false` | Disabled visual state |
645
+ | `loading` | boolean | `false` | Busy visual state |
646
+ | `full-width` | boolean | `false` | Spans full container width |
647
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
648
+
649
+ **Content projection** — children become the alert content.
650
+
651
+ **Accessibility** — renders `role="alert"` and reflects the `loading` state to
652
+ `aria-busy`.
653
+
654
+ **Internal target** — `[data-sp-alert-native]` selects the native `<div>`.
655
+
656
+ ---
657
+
658
+ ### sp-avatar
659
+
660
+ Renders a `<div>` avatar container backed by the Spectre avatar recipe.
661
+
662
+ **Attributes**
663
+
664
+ | Attribute | Type | Default | Description |
665
+ | ------------------------- | ---------------------------- | -------- | -------------------------------- |
666
+ | `size` | `xs \| sm \| md \| lg \| xl` | `md` | Avatar size |
667
+ | `shape` | `circle \| square` | `circle` | Avatar shape |
668
+ | `interactive` | boolean | `false` | Applies interactive styling |
669
+ | `disabled` | boolean | `false` | Disabled visual state |
670
+ | `loading` | boolean | `false` | Busy visual state |
671
+ | `full-width` | boolean | `false` | Spans full container width |
672
+ | `placeholder` | boolean | `false` | Placeholder background and color |
673
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
674
+
675
+ **Content projection** — children become the avatar content (an `<img>`,
676
+ initials, or an icon).
677
+
678
+ **Accessibility** — reflects the `loading` state to `aria-busy`.
679
+
680
+ **Internal target** — `[data-sp-avatar-native]` selects the native `<div>`.
681
+
682
+ ---
683
+
684
+ ### sp-spinner
685
+
686
+ Renders a `<div role="status">` loading indicator backed by the Spectre spinner
687
+ recipe.
688
+
689
+ **Attributes**
690
+
691
+ | Attribute | Type | Default | Description |
692
+ | ------------------------- | ------------------------------------------------------------------------------------------ | ------- | ------------------------------- |
693
+ | `variant` | `primary \| secondary \| success \| warning \| danger \| info \| neutral \| accent \| cta` | — | Arc color |
694
+ | `size` | `sm \| md \| lg` | `md` | Spinner size |
695
+ | `disabled` | boolean | `false` | Disabled visual state |
696
+ | `loading` | boolean | `true` | Busy visual state |
697
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
698
+
699
+ **Accessibility** — renders `role="status"` and reflects the `loading` state to
700
+ `aria-busy`. Defaults `aria-label` to `Loading` unless `aria-label` is provided.
701
+
702
+ **Internal target** — `[data-sp-spinner-native]` selects the native `<div>`.
703
+
704
+ ---
705
+
706
+ ### sp-tag
707
+
708
+ Renders a `<span>` tag/chip backed by the Spectre tag recipe.
709
+
710
+ **Attributes**
711
+
712
+ | Attribute | Type | Default | Description |
713
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------- |
714
+ | `variant` | `default \| primary \| secondary \| success \| warning \| danger \| info \| neutral \| accent \| cta \| outline \| ghost` | `default` | Tag color |
715
+ | `size` | `sm \| md \| lg` | `md` | Tag size |
716
+ | `interactive` | boolean | `false` | Applies interactive styling |
717
+ | `selected` | boolean | `false` | Selected/active visual state |
718
+ | `dismissible` | boolean | `false` | Reserves space for a dismiss icon |
719
+ | `disabled` | boolean | `false` | Disabled visual state |
720
+ | `loading` | boolean | `false` | Busy visual state |
721
+ | `full-width` | boolean | `false` | Spans full container width |
722
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<span>` |
723
+
724
+ **Content projection** — children become the tag label (and any projected
725
+ dismiss icon).
726
+
727
+ **Accessibility** — reflects the `loading` state to `aria-busy`.
728
+
729
+ **Internal target** — `[data-sp-tag-native]` selects the native `<span>`.
730
+
731
+ ---
732
+
733
+ ### sp-pricing-card
734
+
735
+ Renders a `<div>` pricing card container backed by the Spectre pricing-card
736
+ recipe.
737
+
738
+ **Attributes**
739
+
740
+ | Attribute | Type | Default | Description |
741
+ | ------------------------- | ------- | ------- | ------------------------------- |
742
+ | `featured` | boolean | `false` | Highlights the card as featured |
743
+ | `interactive` | boolean | `false` | Applies interactive styling |
744
+ | `disabled` | boolean | `false` | Disabled visual state |
745
+ | `loading` | boolean | `false` | Busy visual state |
746
+ | `full-height` | boolean | `false` | Spans full container height |
747
+ | `id` / `title` / `aria-*` | string | — | Forwarded to the native `<div>` |
748
+
749
+ **Content projection** — children become the pricing card content (heading,
750
+ price, feature list, call-to-action, etc.).
751
+
752
+ **Accessibility** — reflects the `loading` state to `aria-busy`.
753
+
754
+ **Internal target** — `[data-sp-pricing-card-native]` selects the native
755
+ `<div>`.
487
756
 
488
757
  ## Package exports / API surface
489
758
 
@@ -495,52 +764,81 @@ helper.
495
764
  **Bulk registration**
496
765
 
497
766
  ```ts
498
- import { defineSpectreComponents } from '@phcdevworks/spectre-components';
499
- defineSpectreComponents(); // registers all sp-* elements
767
+ import { defineSpectreComponents } from '@phcdevworks/spectre-components'
768
+ defineSpectreComponents() // registers all sp-* elements
500
769
  ```
501
770
 
502
771
  **Per-component helpers** (same as individual entry points):
503
772
  `defineSpectreButton`, `defineSpectreInput`, `defineSpectreTextarea`,
504
773
  `defineSpectreSelect`, `defineSpectreCheckbox`, `defineSpectreRadio`,
505
- `defineSpectreLabel`, `defineSpectreFieldset`
506
-
507
- **Element classes**:
508
- `SpectreButtonElement`, `SpectreInputElement`, `SpectreTextareaElement`,
509
- `SpectreSelectElement`, `SpectreCheckboxElement`, `SpectreRadioElement`,
510
- `SpectreLabelElement`, `SpectreFieldsetElement`
511
-
512
- **Button constants and types**:
513
- `spectreButtonVariants`, `spectreButtonSizes`, `spectreButtonTypes`,
514
- `SpectreButtonVariant`, `SpectreButtonSize`, `SpectreButtonType`,
515
- `SpectreButtonProps`
516
-
517
- **Input / textarea / select constants and types**:
518
- `spectreInputSizes`, `spectreInputTypes`, `SpectreInputSize`,
519
- `SpectreInputType`, `SpectreInputProps`, `SpectreTextareaProps`,
520
- `SpectreSelectProps`
774
+ `defineSpectreLabel`, `defineSpectreFieldset`, `defineSpectreBadge`,
775
+ `defineSpectreCard`, `defineSpectreIconBox`, `defineSpectreRating`,
776
+ `defineSpectreTestimonial`, `defineSpectreAlert`, `defineSpectreAvatar`,
777
+ `defineSpectreSpinner`, `defineSpectreTag`, `defineSpectrePricingCard`
778
+
779
+ **Element classes**: `SpectreButtonElement`, `SpectreInputElement`,
780
+ `SpectreTextareaElement`, `SpectreSelectElement`, `SpectreCheckboxElement`,
781
+ `SpectreRadioElement`, `SpectreLabelElement`, `SpectreFieldsetElement`,
782
+ `SpectreBadgeElement`, `SpectreCardElement`, `SpectreIconBoxElement`,
783
+ `SpectreRatingElement`, `SpectreTestimonialElement`, `SpectreAlertElement`,
784
+ `SpectreAvatarElement`, `SpectreSpinnerElement`, `SpectreTagElement`,
785
+ `SpectrePricingCardElement`
786
+
787
+ **Button constants and types**: `spectreButtonVariants`, `spectreButtonSizes`,
788
+ `spectreButtonTypes`, `SpectreButtonVariant`, `SpectreButtonSize`,
789
+ `SpectreButtonType`, `SpectreButtonProps`
790
+
791
+ **Input / textarea / select constants and types**: `spectreInputSizes`,
792
+ `spectreInputTypes`, `SpectreInputSize`, `SpectreInputType`,
793
+ `SpectreInputProps`, `SpectreTextareaProps`, `SpectreSelectProps`
521
794
 
522
795
  **Props interfaces** (checkbox / radio / label / fieldset):
523
796
  `SpectreCheckboxProps`, `SpectreRadioProps`, `SpectreLabelProps`,
524
797
  `SpectreFieldsetProps`
525
798
 
799
+ **Display constants and types**: `spectreBadgeVariants`, `spectreBadgeSizes`,
800
+ `spectreCardVariants`, `spectreIconBoxVariants`, `spectreIconBoxSizes`,
801
+ `spectreRatingSizes`, `spectreTestimonialVariants`, `spectreAlertVariants`,
802
+ `spectreAlertSizes`, `spectreAvatarShapes`, `spectreAvatarSizes`,
803
+ `spectreSpinnerVariants`, `spectreSpinnerSizes`, `spectreTagVariants`,
804
+ `spectreTagSizes`, `SpectreBadgeVariant`, `SpectreBadgeSize`,
805
+ `SpectreCardVariant`, `SpectreIconBoxVariant`, `SpectreIconBoxSize`,
806
+ `SpectreRatingSize`, `SpectreTestimonialVariant`, `SpectreAlertVariant`,
807
+ `SpectreAlertSize`, `SpectreAvatarShape`, `SpectreAvatarSize`,
808
+ `SpectreSpinnerVariant`, `SpectreSpinnerSize`, `SpectreTagVariant`,
809
+ `SpectreTagSize`, `SpectreBadgeProps`, `SpectreCardProps`,
810
+ `SpectreIconBoxProps`, `SpectreRatingProps`, `SpectreTestimonialProps`,
811
+ `SpectreAlertProps`, `SpectreAvatarProps`, `SpectreSpinnerProps`,
812
+ `SpectreTagProps`, `SpectrePricingCardProps`
813
+
526
814
  ### Subpath entry points
527
815
 
528
816
  Each entry point registers only that component and exports only its surface:
529
817
 
530
- | Entry point | Registers | Key exports |
531
- |-------------|-----------|-------------|
532
- | `.../button` | `sp-button` | `defineSpectreButton`, `SpectreButtonElement`, button constants and types |
533
- | `.../input` | `sp-input` | `defineSpectreInput`, `SpectreInputElement`, input constants and types |
534
- | `.../textarea` | `sp-textarea` | `defineSpectreTextarea`, `SpectreTextareaElement`, `SpectreTextareaProps` |
535
- | `.../select` | `sp-select` | `defineSpectreSelect`, `SpectreSelectElement`, `SpectreSelectProps` |
536
- | `.../checkbox` | `sp-checkbox` | `defineSpectreCheckbox`, `SpectreCheckboxElement`, `SpectreCheckboxProps` |
537
- | `.../radio` | `sp-radio` | `defineSpectreRadio`, `SpectreRadioElement`, `SpectreRadioProps` |
538
- | `.../label` | `sp-label` | `defineSpectreLabel`, `SpectreLabelElement`, `SpectreLabelProps` |
539
- | `.../fieldset` | `sp-fieldset` | `defineSpectreFieldset`, `SpectreFieldsetElement`, `SpectreFieldsetProps` |
818
+ | Entry point | Registers | Key exports |
819
+ | ------------------ | ----------------- | ---------------------------------------------------------------------------------------- |
820
+ | `.../button` | `sp-button` | `defineSpectreButton`, `SpectreButtonElement`, button constants and types |
821
+ | `.../input` | `sp-input` | `defineSpectreInput`, `SpectreInputElement`, input constants and types |
822
+ | `.../textarea` | `sp-textarea` | `defineSpectreTextarea`, `SpectreTextareaElement`, `SpectreTextareaProps` |
823
+ | `.../select` | `sp-select` | `defineSpectreSelect`, `SpectreSelectElement`, `SpectreSelectProps` |
824
+ | `.../checkbox` | `sp-checkbox` | `defineSpectreCheckbox`, `SpectreCheckboxElement`, `SpectreCheckboxProps` |
825
+ | `.../radio` | `sp-radio` | `defineSpectreRadio`, `SpectreRadioElement`, `SpectreRadioProps` |
826
+ | `.../label` | `sp-label` | `defineSpectreLabel`, `SpectreLabelElement`, `SpectreLabelProps` |
827
+ | `.../fieldset` | `sp-fieldset` | `defineSpectreFieldset`, `SpectreFieldsetElement`, `SpectreFieldsetProps` |
828
+ | `.../badge` | `sp-badge` | `defineSpectreBadge`, `SpectreBadgeElement`, badge constants and types |
829
+ | `.../card` | `sp-card` | `defineSpectreCard`, `SpectreCardElement`, card constants and types |
830
+ | `.../icon-box` | `sp-icon-box` | `defineSpectreIconBox`, `SpectreIconBoxElement`, icon-box constants and types |
831
+ | `.../rating` | `sp-rating` | `defineSpectreRating`, `SpectreRatingElement`, rating constants and types |
832
+ | `.../testimonial` | `sp-testimonial` | `defineSpectreTestimonial`, `SpectreTestimonialElement`, testimonial constants and types |
833
+ | `.../alert` | `sp-alert` | `defineSpectreAlert`, `SpectreAlertElement`, alert constants and types |
834
+ | `.../avatar` | `sp-avatar` | `defineSpectreAvatar`, `SpectreAvatarElement`, avatar constants and types |
835
+ | `.../spinner` | `sp-spinner` | `defineSpectreSpinner`, `SpectreSpinnerElement`, spinner constants and types |
836
+ | `.../tag` | `sp-tag` | `defineSpectreTag`, `SpectreTagElement`, tag constants and types |
837
+ | `.../pricing-card` | `sp-pricing-card` | `defineSpectrePricingCard`, `SpectrePricingCardElement`, `SpectrePricingCardProps` |
540
838
 
541
839
  Size constants are shared between input, textarea, and select. Import
542
- `spectreInputSizes` / `SpectreInputSize` from `.../input` when needed
543
- alongside textarea or select.
840
+ `spectreInputSizes` / `SpectreInputSize` from `.../input` when needed alongside
841
+ textarea or select.
544
842
 
545
843
  ## Relationship to the rest of Spectre
546
844
 
@@ -552,8 +850,8 @@ spectre-components → Lit web component behavior ← you are here
552
850
  ```
553
851
 
554
852
  The Golden Rule: **tokens define meaning, UI defines structure, components
555
- define behavior, adapters define delivery.** This package only owns the
556
- behavior layer.
853
+ define behavior, adapters define delivery.** This package only owns the behavior
854
+ layer.
557
855
 
558
856
  ## Development
559
857
 
@@ -561,20 +859,23 @@ behavior layer.
561
859
  git clone https://github.com/phcdevworks/spectre-components.git
562
860
  cd spectre-components
563
861
  npm install
564
- npm run check # lint + typecheck + test + build + export validation
862
+ npm run check # full release validation gate
565
863
  ```
566
864
 
567
- Requires Node.js `^22.12.0 || >=24.0.0` and npm `11.14.1`.
568
-
569
- | Command | Purpose |
570
- |---------|---------|
571
- | `npm run check` | Full validation (lint → typecheck → test → build → export check) |
572
- | `npm run build` | Compile ESM + CJS with declarations into `dist/` |
573
- | `npm test` | Run Vitest suite under happy-dom |
574
- | `npm run lint` | ESLint |
575
- | `npm run check:exports` | Verify built subpath exports resolve correctly |
576
- | `npm run dev` | tsup watch mode |
577
- | `npm run clean` | Remove `dist/` and `coverage/` |
865
+ Requires Node.js `^22.13.0 || >=24.0.0` and npm `11.16.0`.
866
+
867
+ | Command | Purpose |
868
+ | -------------------------- | ----------------------------------------------------------------------------------------------------- |
869
+ | `npm run check` | Full validation (lint → typecheck → test → build → export, contract, invariant, and ecosystem checks) |
870
+ | `npm run build` | Compile ESM + CJS with declarations into `dist/` |
871
+ | `npm test` | Run Vitest suite under happy-dom |
872
+ | `npm run lint` | ESLint |
873
+ | `npm run check:exports` | Verify built subpath exports resolve correctly |
874
+ | `npm run check:contract` | Verify built exports match `components.contract.json` |
875
+ | `npm run check:invariants` | Verify light-DOM and no-hardcoded-visual invariants |
876
+ | `npm run check:ecosystem` | Validate `spectre.manifest.json` |
877
+ | `npm run dev` | tsup watch mode |
878
+ | `npm run clean` | Remove `dist/` and `coverage/` |
578
879
 
579
880
  Key source areas:
580
881
 
@@ -582,27 +883,27 @@ Key source areas:
582
883
  - `src/utils/` — `base.ts`, `projectable.ts`, `form.ts`, `dom.ts`
583
884
  - `src/index.ts` — root public API and bulk registration helper
584
885
  - `tests/` — component behavior coverage (Vitest + happy-dom)
585
- - `scripts/check-exports.js` — post-build export resolution check
886
+ - `scripts/check-exports.ts` — post-build export resolution check
586
887
 
587
888
  ## Troubleshooting
588
889
 
589
- **Build fails with type errors** — TypeScript 6 is required. Run
590
- `npm install`, then `npm run build`.
890
+ **Build fails with type errors** — TypeScript 6 is required. Run `npm install`,
891
+ then `npm run build`.
591
892
 
592
893
  **Tests fail in CI but pass locally** — Tests run under happy-dom. Confirm you
593
- are on Node `^22.12.0 || >=24.0.0`. CI tests both versions.
894
+ are on Node `^22.13.0 || >=24.0.0`. CI tests both versions.
594
895
 
595
896
  **Custom element already defined** — Each `defineSpectre*()` helper is
596
897
  idempotent; calling it twice is safe. If you see conflicts, two different
597
898
  versions of this package may be loaded in the same page.
598
899
 
599
- **Styles are not applying** — The Spectre CSS layers must load before
600
- components are registered. Import `@phcdevworks/spectre-tokens/index.css` and
900
+ **Styles are not applying** — The Spectre CSS layers must load before components
901
+ are registered. Import `@phcdevworks/spectre-tokens/index.css` and
601
902
  `@phcdevworks/spectre-ui/index.css` at the top of your entry module.
602
903
 
603
904
  **Properties not reflecting in React 18** — React 18 sets custom element
604
- properties as attributes. Use a `ref` to set properties imperatively, or
605
- upgrade to React 19 which supports custom elements fully.
905
+ properties as attributes. Use a `ref` to set properties imperatively, or upgrade
906
+ to React 19 which supports custom elements fully.
606
907
 
607
908
  ## Validation
608
909
 
@@ -612,8 +913,9 @@ Run the full validation gate before any pull request:
612
913
  npm run check
613
914
  ```
614
915
 
615
- This runs: lint → typecheck → tests → build → export validation. All steps
616
- must pass.
916
+ This runs: lint → typecheck → tests → build → export validation → contract
917
+ validation → invariant checks → ecosystem manifest validation. All steps must
918
+ pass.
617
919
 
618
920
  ## AI and automation boundaries
619
921
 
@@ -640,10 +942,10 @@ Contribution boundaries:
640
942
 
641
943
  - Components must consume `@phcdevworks/spectre-ui` class helpers — do not
642
944
  recreate CSS locally.
643
- - Design values must come from `@phcdevworks/spectre-tokens` — do not
644
- hardcode colors, spacing, or other visual primitives.
645
- - Component tags, properties, events, slots, and ARIA behavior are stable API
646
- — breaking changes require a semver major bump.
945
+ - Design values must come from `@phcdevworks/spectre-tokens` — do not hardcode
946
+ colors, spacing, or other visual primitives.
947
+ - Component tags, properties, events, slots, and ARIA behavior are stable API —
948
+ breaking changes require a semver major bump.
647
949
  - Render in light DOM only — Shadow DOM changes require design-system approval.
648
950
  - No framework-specific code — no JSX, SFCs, or Astro components in this
649
951
  package.