@phcdevworks/spectre-components 1.1.0 → 1.3.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 (64) hide show
  1. package/README.md +559 -153
  2. package/dist/{base-2rD8EmdC.d.cts → base-BRQ7QAAr.d.cts} +9 -0
  3. package/dist/{base-2rD8EmdC.d.ts → base-BRQ7QAAr.d.ts} +9 -0
  4. package/dist/button.cjs +146 -35
  5. package/dist/button.cjs.map +1 -1
  6. package/dist/button.d.cts +34 -29
  7. package/dist/button.d.ts +34 -29
  8. package/dist/button.js +146 -35
  9. package/dist/button.js.map +1 -1
  10. package/dist/checkbox.cjs +113 -6
  11. package/dist/checkbox.cjs.map +1 -1
  12. package/dist/checkbox.d.cts +21 -19
  13. package/dist/checkbox.d.ts +21 -19
  14. package/dist/checkbox.js +113 -6
  15. package/dist/checkbox.js.map +1 -1
  16. package/dist/fieldset.cjs +126 -5
  17. package/dist/fieldset.cjs.map +1 -1
  18. package/dist/fieldset.d.cts +36 -7
  19. package/dist/fieldset.d.ts +36 -7
  20. package/dist/fieldset.js +126 -5
  21. package/dist/fieldset.js.map +1 -1
  22. package/dist/form-yR_u97J-.d.cts +10 -0
  23. package/dist/form-yR_u97J-.d.ts +10 -0
  24. package/dist/index.cjs +530 -96
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +4 -4
  27. package/dist/index.d.ts +4 -4
  28. package/dist/index.js +530 -96
  29. package/dist/index.js.map +1 -1
  30. package/dist/input.cjs +138 -7
  31. package/dist/input.cjs.map +1 -1
  32. package/dist/input.d.cts +49 -23
  33. package/dist/input.d.ts +49 -23
  34. package/dist/input.js +138 -7
  35. package/dist/input.js.map +1 -1
  36. package/dist/label.cjs +98 -3
  37. package/dist/label.cjs.map +1 -1
  38. package/dist/label.d.cts +19 -6
  39. package/dist/label.d.ts +19 -6
  40. package/dist/label.js +98 -3
  41. package/dist/label.js.map +1 -1
  42. package/dist/{projectable-BzDQ4xp_.d.cts → projectable-Bx-IVk0w.d.cts} +1 -1
  43. package/dist/{projectable-s3SgvjGX.d.ts → projectable-D6IDeKfr.d.ts} +1 -1
  44. package/dist/radio.cjs +133 -6
  45. package/dist/radio.cjs.map +1 -1
  46. package/dist/radio.d.cts +23 -19
  47. package/dist/radio.d.ts +23 -19
  48. package/dist/radio.js +133 -6
  49. package/dist/radio.js.map +1 -1
  50. package/dist/select.cjs +120 -8
  51. package/dist/select.cjs.map +1 -1
  52. package/dist/select.d.cts +24 -22
  53. package/dist/select.d.ts +24 -22
  54. package/dist/select.js +120 -8
  55. package/dist/select.js.map +1 -1
  56. package/dist/textarea.cjs +134 -7
  57. package/dist/textarea.cjs.map +1 -1
  58. package/dist/textarea.d.cts +37 -22
  59. package/dist/textarea.d.ts +37 -22
  60. package/dist/textarea.js +134 -7
  61. package/dist/textarea.js.map +1 -1
  62. package/package.json +24 -12
  63. package/dist/form-iI7vV-3W.d.cts +0 -6
  64. package/dist/form-iI7vV-3W.d.ts +0 -6
package/README.md CHANGED
@@ -1,26 +1,74 @@
1
1
  # @phcdevworks/spectre-components
2
2
 
3
- [![GitHub issues](https://img.shields.io/github/issues/phcdevworks/spectre-components)](https://github.com/phcdevworks/spectre-components/issues)
4
- [![GitHub pull requests](https://img.shields.io/github/issues-pr/phcdevworks/spectre-components)](https://github.com/phcdevworks/spectre-components/pulls)
5
- [![License](https://img.shields.io/github/license/phcdevworks/spectre-components)](LICENSE)
6
-
7
- `@phcdevworks/spectre-components` is the Lit-based component layer of the
8
- Spectre suite.
9
-
10
- Maintained by PHCDevworks, it turns Spectre tokens and Spectre UI contracts
11
- into reusable, accessible, framework-agnostic web components. It is the
12
- canonical component implementation layer for Spectre, built on platform
13
- standards and designed to be consumed directly or wrapped later by adapter
14
- packages.
3
+ [![npm version](https://img.shields.io/npm/v/@phcdevworks/spectre-components)](https://www.npmjs.com/package/@phcdevworks/spectre-components)
4
+ [![CI](https://github.com/phcdevworks/spectre-components/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/phcdevworks/spectre-components/actions/workflows/ci.yml)
5
+ [![License](https://img.shields.io/npm/l/@phcdevworks/spectre-components)](LICENSE)
6
+ [![Node](https://img.shields.io/node/v/@phcdevworks/spectre-components)](https://nodejs.org)
7
+
8
+ `@phcdevworks/spectre-components` is the **Layer 3** Lit-based web component
9
+ package of the Spectre design system. It turns Spectre tokens
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.
14
+
15
+ [Contributing](CONTRIBUTING.md) | [Code of Conduct](CODE_OF_CONDUCT.md) |
16
+ [Changelog](CHANGELOG.md) | [Roadmap](ROADMAP.md) |
17
+ [Security Policy](SECURITY.md)
18
+
19
+ ## Why this package exists alongside spectre-ui
20
+
21
+ `@phcdevworks/spectre-ui` owns CSS: class recipes, Tailwind helpers, and the
22
+ styling contract that maps Spectre tokens to visual output. It ships CSS rules
23
+ and JavaScript class-name helpers — nothing more.
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.
29
+
30
+ The separation keeps each layer focused:
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 |
36
+ | **L3** | **`@phcdevworks/spectre-components`** | **Lit web component behavior and API** |
37
+ | L4 | Downstream adapters | Framework-specific delivery |
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.
15
42
 
16
43
  ## Key capabilities
17
44
 
18
- - Ships reusable web components implemented with Lit
19
- - Consumes `@phcdevworks/spectre-tokens` as the source of visual meaning
20
- - Consumes `@phcdevworks/spectre-ui` as the styling contract layer
21
- - Keeps component delivery framework-agnostic through custom elements
22
- - Builds accessibility into the initial component patterns
23
- - Exposes a small, explicit API surface suitable for long-term growth
45
+ - Lit-based custom elements on the Custom Elements standard
46
+ - Renders in **light DOM** so `@phcdevworks/spectre-ui` global styles apply
47
+ directly — no Shadow DOM piercing required
48
+ - ARIA attributes (`aria-label`, `aria-labelledby`, `aria-describedby`) are
49
+ forwarded to the native element, not left on the host
50
+ - Focus and blur delegate to the inner native element
51
+ - Property validation with safe fallbacks in `willUpdate()`
52
+ - Idempotent `defineSpectre*()` helpers — safe to call multiple times
53
+ - ESM + CJS dual build with TypeScript declaration files
54
+ - Tree-shakeable subpath exports per component
55
+
56
+ ## When to use this package
57
+
58
+ - You are building UI with the Spectre design system and want standards-based
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.
62
+ - You are writing a framework adapter (React, Vue, Astro) and need a reliable,
63
+ stable element layer to wrap.
64
+
65
+ ## When not to use this package
66
+
67
+ - 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.
70
+ - You need framework-specific component files (JSX, SFCs, Astro components) —
71
+ those belong in a downstream adapter package.
24
72
 
25
73
  ## Installation
26
74
 
@@ -30,220 +78,578 @@ npm install @phcdevworks/spectre-components @phcdevworks/spectre-ui @phcdevworks
30
78
 
31
79
  ## Quick start
32
80
 
33
- Import the Spectre CSS layers first, then register the custom elements you want
34
- to use:
81
+ ### Plain HTML
35
82
 
36
- ```ts
37
- import "@phcdevworks/spectre-tokens/index.css";
38
- import "@phcdevworks/spectre-ui/index.css";
83
+ Import the CSS layers and register all components from a script tag or entry
84
+ module. These are standard custom elements — no build step required for
85
+ consumption.
86
+
87
+ ```html
88
+ <!doctype html>
89
+ <html lang="en">
90
+ <head>
91
+ <!-- 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" />
94
+ </head>
95
+ <body>
96
+ <sp-label for="email">Email address</sp-label>
97
+ <sp-input id="email" name="email" type="email" placeholder="you@example.com"></sp-input>
98
+
99
+ <sp-button variant="primary" type="submit">Send</sp-button>
100
+ <sp-button variant="ghost" type="button">Cancel</sp-button>
101
+
102
+ <script type="module">
103
+ import { defineSpectreComponents } from '/node_modules/@phcdevworks/spectre-components/dist/index.js';
104
+ defineSpectreComponents();
105
+ </script>
106
+ </body>
107
+ </html>
108
+ ```
39
109
 
40
- import { defineSpectreComponents } from "@phcdevworks/spectre-components";
110
+ ### JavaScript / TypeScript module
111
+
112
+ ```ts
113
+ import '@phcdevworks/spectre-tokens/index.css';
114
+ import '@phcdevworks/spectre-ui/index.css';
41
115
 
116
+ // Register everything at once
117
+ import { defineSpectreComponents } from '@phcdevworks/spectre-components';
42
118
  defineSpectreComponents();
119
+
120
+ // 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();
43
125
  ```
44
126
 
45
- Use the components in markup:
127
+ ### Full form example
46
128
 
47
129
  ```html
48
- <sp-button variant="primary" size="md">Save</sp-button>
49
- <sp-button variant="ghost" size="sm">Cancel</sp-button>
50
- <sp-input name="email" type="email" placeholder="Email address"></sp-input>
51
- <sp-textarea name="bio" rows="4" placeholder="Short bio"></sp-textarea>
52
- <sp-select name="role">
53
- <option value="admin">Admin</option>
54
- <option value="user">User</option>
55
- </sp-select>
56
- <sp-checkbox name="terms" value="accepted">Accept terms</sp-checkbox>
57
- <sp-radio name="plan" value="monthly">Monthly</sp-radio>
58
- <sp-label for="email">Email address</sp-label>
59
130
  <sp-fieldset legend="Contact preferences">
60
- <sp-checkbox name="email-updates">Email updates</sp-checkbox>
131
+ <sp-label for="email">Email address</sp-label>
132
+ <sp-input id="email" name="email" type="email" required></sp-input>
133
+
134
+ <sp-label for="bio">Bio</sp-label>
135
+ <sp-textarea id="bio" name="bio" rows="4" maxlength="500"></sp-textarea>
136
+
137
+ <sp-label for="role">Role</sp-label>
138
+ <sp-select id="role" name="role">
139
+ <option value="admin">Admin</option>
140
+ <option value="user">User</option>
141
+ </sp-select>
142
+
143
+ <sp-checkbox name="terms" value="accepted" required>
144
+ I accept the <a href="/terms">terms of service</a>
145
+ </sp-checkbox>
146
+
147
+ <sp-radio name="plan" value="monthly">Monthly billing</sp-radio>
148
+ <sp-radio name="plan" value="annual">Annual billing</sp-radio>
149
+
150
+ <sp-button variant="primary" type="submit">Save</sp-button>
151
+ <sp-button variant="ghost" type="reset">Reset</sp-button>
61
152
  </sp-fieldset>
62
153
  ```
63
154
 
64
- Register only what you need using the per-component entry points:
155
+ ### Framework integration note
65
156
 
157
+ These are standard HTML custom elements. They work in every major framework
158
+ that supports the Custom Elements standard:
159
+
160
+ **React 19+** — supports custom element properties and events natively:
161
+ ```tsx
162
+ // React 19: properties and events work directly
163
+ <sp-input name="email" type="email" onInput={(e) => setValue(e.target.value)} />
164
+ ```
165
+
166
+ **React 18 and below** — set attributes via `ref` for properties, listen for
167
+ native events on the element:
168
+ ```tsx
169
+ const inputRef = useRef(null);
170
+ useEffect(() => {
171
+ if (inputRef.current) inputRef.current.invalid = true;
172
+ }, []);
173
+ <sp-input ref={inputRef} name="email" />
174
+ ```
175
+
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:
66
179
  ```ts
67
- import { defineSpectreButton } from "@phcdevworks/spectre-components/button";
68
- import { defineSpectreInput } from "@phcdevworks/spectre-components/input";
69
- import { defineSpectreTextarea } from "@phcdevworks/spectre-components/textarea";
70
- import { defineSpectreSelect } from "@phcdevworks/spectre-components/select";
71
- import { defineSpectreCheckbox } from "@phcdevworks/spectre-components/checkbox";
72
- import { defineSpectreRadio } from "@phcdevworks/spectre-components/radio";
73
- import { defineSpectreLabel } from "@phcdevworks/spectre-components/label";
74
- import { defineSpectreFieldset } from "@phcdevworks/spectre-components/fieldset";
180
+ // vite.config.ts
181
+ plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('sp-') } } })]
182
+ ```
183
+ ```html
184
+ <sp-input name="email" :invalid="hasError" @change="handleChange" />
75
185
  ```
76
186
 
77
- ## What this package owns
187
+ **Astro** — use components as static custom elements or with `client:load`
188
+ when JavaScript interactivity is needed:
189
+ ```astro
190
+ ---
191
+ import '@phcdevworks/spectre-tokens/index.css';
192
+ import '@phcdevworks/spectre-ui/index.css';
193
+ ---
194
+ <script>
195
+ import { defineSpectreComponents } from '@phcdevworks/spectre-components';
196
+ defineSpectreComponents();
197
+ </script>
198
+ <sp-button variant="primary">Click me</sp-button>
199
+ ```
78
200
 
79
- - Lit-based web component implementation for Spectre
80
- - Accessible component behavior and DOM structure
81
- - Public custom element registration helpers
82
- - Component-level TypeScript APIs for future adapters to build on
201
+ > Framework adapter packages that wrap these components into idiomatic JSX or
202
+ > SFC APIs belong in a downstream adapter — not in this package.
83
203
 
84
- Golden rule: implement components from Spectre contracts, do not redefine those
85
- contracts locally.
204
+ ## Accessibility
86
205
 
87
- ## What this package does not own
206
+ All components follow WCAG 2.1 AA baseline expectations by default.
88
207
 
89
- - Design-token values or semantic visual meaning That belongs to
90
- [`@phcdevworks/spectre-tokens`](https://github.com/phcdevworks/spectre-tokens).
91
- - CSS utilities, class recipes, or styling contracts That belongs to
92
- [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
93
- - Framework adapters such as React, Vue, Astro, or app-specific wrappers Those
94
- belong in downstream adapter packages.
95
- - Routing, shell logic, manifest behavior, or startup orchestration Those are
96
- outside the scope of this package.
208
+ **ARIA attribute forwarding** — `aria-label`, `aria-labelledby`, and
209
+ `aria-describedby` set on the host element are automatically forwarded to the
210
+ inner native element so screen readers receive them on the correct target.
97
211
 
98
- ## Package exports / API surface
212
+ **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.
99
215
 
100
- ### Root package
216
+ **State communication**
101
217
 
102
- `@phcdevworks/spectre-components` exports everything from all component entry
103
- points plus the `defineSpectreComponents()` bulk registration helper.
218
+ | State | ARIA effect |
219
+ |-------|-------------|
220
+ | `loading` | `aria-busy="true"` on the native element |
221
+ | `invalid` | `aria-invalid="true"` on the native element |
222
+ | `disabled` | native `disabled` attribute (removes from tab order) |
223
+ | `required` | native `required` attribute |
104
224
 
105
- **Registration helpers**
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.
106
228
 
107
- - `defineSpectreComponents()` — registers all components at once
108
- - `defineSpectreButton()`, `defineSpectreInput()`, `defineSpectreTextarea()`
109
- - `defineSpectreSelect()`, `defineSpectreCheckbox()`, `defineSpectreRadio()`
110
- - `defineSpectreLabel()`, `defineSpectreFieldset()`
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.
111
232
 
112
- **Element classes**
233
+ **Keyboard behavior** — provided entirely by the native element inside each
234
+ component. No custom keyboard handling is layered on top.
113
235
 
114
- - `SpectreButtonElement`, `SpectreInputElement`, `SpectreTextareaElement`
115
- - `SpectreSelectElement`, `SpectreCheckboxElement`, `SpectreRadioElement`
116
- - `SpectreLabelElement`, `SpectreFieldsetElement`
236
+ ## Light DOM rendering
117
237
 
118
- **Constants and types (button)**
238
+ 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.
119
241
 
120
- - `spectreButtonVariants`, `spectreButtonSizes`, `spectreButtonTypes`
121
- - `SpectreButtonVariant`, `SpectreButtonSize`, `SpectreButtonType`
122
- - `SpectreButtonProps`
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:
123
245
 
124
- **Constants and types (input / textarea / select)**
246
+ ```css
247
+ /* Target the native input inside sp-input */
248
+ sp-input input { font-size: 0.875rem; }
125
249
 
126
- - `spectreInputSizes`, `spectreInputTypes`
127
- - `SpectreInputSize`, `SpectreInputType`
128
- - `SpectreInputProps`, `SpectreTextareaProps`, `SpectreSelectProps`
250
+ /* Stable internal hook — won't break if markup restructures */
251
+ sp-input [data-sp-input-native] { font-size: 0.875rem; }
252
+ ```
129
253
 
130
- **Props interfaces (checkbox / radio / label / fieldset)**
254
+ Do not switch any component from light DOM to Shadow DOM without a
255
+ design-system-level decision.
131
256
 
132
- - `SpectreCheckboxProps`, `SpectreRadioProps`
133
- - `SpectreLabelProps`, `SpectreFieldsetProps`
257
+ ## Components
134
258
 
135
- ### Button entry point
259
+ ### sp-button
260
+
261
+ Renders a `<button>` with Spectre variant, size, loading, and pill support.
262
+
263
+ **Attributes**
264
+
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>` |
285
+
286
+ **Events** — native button events bubble normally (`click`, `focus`, `blur`).
287
+
288
+ **Content projection** — place children inside `<sp-button>` to use them as
289
+ button content instead of the `label` property:
136
290
 
137
- `@phcdevworks/spectre-components/button` — registers only `sp-button`.
291
+ ```html
292
+ <sp-button variant="primary">
293
+ <svg aria-hidden="true">...</svg>
294
+ Save changes
295
+ </sp-button>
296
+ ```
138
297
 
139
- Exports: `defineSpectreButton`, `SpectreButtonElement`, `SpectreButtonProps`,
140
- `spectreButtonVariants`, `spectreButtonSizes`, `spectreButtonTypes`,
141
- `SpectreButtonVariant`, `SpectreButtonSize`, `SpectreButtonType`.
298
+ **Internal target** — `[data-sp-button-native]` selects the native `<button>`.
142
299
 
143
- ### Input entry point
300
+ ---
144
301
 
145
- `@phcdevworks/spectre-components/input` — registers only `sp-input`.
302
+ ### sp-input
146
303
 
147
- Exports: `defineSpectreInput`, `SpectreInputElement`, `SpectreInputProps`,
148
- `spectreInputSizes`, `spectreInputTypes`, `SpectreInputSize`, `SpectreInputType`.
304
+ Renders an `<input>` with state, size, and type support.
149
305
 
150
- ### Textarea entry point
306
+ **Attributes**
151
307
 
152
- `@phcdevworks/spectre-components/textarea` — registers only `sp-textarea`.
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>` |
153
330
 
154
- Exports: `defineSpectreTextarea`, `SpectreTextareaElement`, `SpectreTextareaProps`.
331
+ **Events** — `input` and `change` fire from the native `<input>` and bubble.
155
332
 
156
- Size constants shared with input: import `spectreInputSizes` / `SpectreInputSize`
157
- from `@phcdevworks/spectre-components/input`.
333
+ **Internal target** — `[data-sp-input-native]` selects the native `<input>`.
158
334
 
159
- ### Select entry point
335
+ ---
160
336
 
161
- `@phcdevworks/spectre-components/select` — registers only `sp-select`.
337
+ ### sp-textarea
162
338
 
163
- Exports: `defineSpectreSelect`, `SpectreSelectElement`, `SpectreSelectProps`.
339
+ Renders a `<textarea>` with row control and resize support.
164
340
 
165
- Size constants shared with input: import `spectreInputSizes` / `SpectreInputSize`
166
- from `@phcdevworks/spectre-components/input`.
341
+ **Attributes** — same as `sp-input` except no `type`, `min`, `max`, `step`, and adds:
167
342
 
168
- ### Checkbox entry point
343
+ | Attribute | Type | Default | Description |
344
+ |-----------|------|---------|-------------|
345
+ | `rows` | number | `2` | Visible row height |
169
346
 
170
- `@phcdevworks/spectre-components/checkbox` — registers only `sp-checkbox`.
347
+ **Events** — `input` and `change` fire from the native `<textarea>`.
171
348
 
172
- Exports: `defineSpectreCheckbox`, `SpectreCheckboxElement`, `SpectreCheckboxProps`.
349
+ **Internal target** — `[data-sp-textarea-native]` selects the native `<textarea>`.
173
350
 
174
- ### Radio entry point
351
+ ---
175
352
 
176
- `@phcdevworks/spectre-components/radio` — registers only `sp-radio`.
353
+ ### sp-select
177
354
 
178
- Exports: `defineSpectreRadio`, `SpectreRadioElement`, `SpectreRadioProps`.
355
+ Renders a `<select>`. Pass `<option>` elements as children — they are
356
+ projected into the native select element.
179
357
 
180
- ### Label entry point
358
+ **Attributes** — same as `sp-input` minus `type`, `placeholder`, `readonly`,
359
+ `inputmode`, `min`, `max`, `step`, `minlength`, `maxlength`.
181
360
 
182
- `@phcdevworks/spectre-components/label` — registers only `sp-label`.
361
+ **Events** — `input` and `change` fire from the native `<select>`.
183
362
 
184
- Exports: `defineSpectreLabel`, `SpectreLabelElement`, `SpectreLabelProps`.
363
+ **Content projection** — `<option>` and `<optgroup>` children are moved into
364
+ the native `<select>`:
185
365
 
186
- ### Fieldset entry point
366
+ ```html
367
+ <sp-select name="country" required>
368
+ <option value="">Select a country</option>
369
+ <optgroup label="Americas">
370
+ <option value="us">United States</option>
371
+ <option value="ca">Canada</option>
372
+ </optgroup>
373
+ </sp-select>
374
+ ```
187
375
 
188
- `@phcdevworks/spectre-components/fieldset` — registers only `sp-fieldset`.
376
+ **Internal target** — `[data-sp-select-native]` selects the native `<select>`.
189
377
 
190
- Exports: `defineSpectreFieldset`, `SpectreFieldsetElement`, `SpectreFieldsetProps`.
378
+ ---
191
379
 
192
- ## Relationship to the rest of Spectre
380
+ ### sp-checkbox
193
381
 
194
- Spectre keeps responsibilities separate:
382
+ Renders a `<label>` wrapping an `<input type="checkbox">` with indicator.
195
383
 
196
- - [`@phcdevworks/spectre-tokens`](https://github.com/phcdevworks/spectre-tokens)
197
- defines design values and semantic meaning
198
- - [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui)
199
- provides CSS, Tailwind helpers, recipes, and styling contracts
200
- - `@phcdevworks/spectre-components` turns those layers into Lit-based reusable
201
- web components
384
+ **Attributes**
202
385
 
203
- That separation keeps visual meaning centralized, styling contracts stable, and
204
- component behavior reusable across frameworks.
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>` |
205
398
 
206
- ## Components
399
+ **Events** — `input` and `change` fire from the native checkbox input.
207
400
 
208
- | Element | Tag | Description |
209
- |---|---|---|
210
- | `SpectreButtonElement` | `sp-button` | Button with variant, size, loading, and pill support |
211
- | `SpectreInputElement` | `sp-input` | Text input with state, size, and type support |
212
- | `SpectreTextareaElement` | `sp-textarea` | Multiline text input with resizable rows |
213
- | `SpectreSelectElement` | `sp-select` | Native select with projected option elements |
214
- | `SpectreCheckboxElement` | `sp-checkbox` | Checkbox with projected or property-based label |
215
- | `SpectreRadioElement` | `sp-radio` | Radio button with projected or property-based label |
216
- | `SpectreLabelElement` | `sp-label` | Accessible label with `for` forwarding |
217
- | `SpectreFieldsetElement` | `sp-fieldset` | Fieldset group with legend text and slot support |
401
+ **Content projection** — children become the label content (supports rich
402
+ markup):
218
403
 
219
- ## Development
404
+ ```html
405
+ <sp-checkbox name="terms" value="accepted" required>
406
+ I accept the <a href="/terms">terms of service</a>
407
+ </sp-checkbox>
408
+ ```
409
+
410
+ **Internal target** — `[data-sp-checkbox-native]` selects the native checkbox.
411
+
412
+ ---
413
+
414
+ ### sp-radio
415
+
416
+ Renders a `<label>` wrapping an `<input type="radio">` with indicator.
417
+ Group multiple `sp-radio` elements by giving them the same `name`.
418
+
419
+ **Attributes** — same as `sp-checkbox`. `value` defaults to `on`.
420
+
421
+ **Events** — `input` and `change` fire from the native radio input.
422
+
423
+ **Content projection** — same as `sp-checkbox`.
424
+
425
+ ```html
426
+ <sp-radio name="plan" value="monthly">Monthly — $9/mo</sp-radio>
427
+ <sp-radio name="plan" value="annual">Annual — $90/yr</sp-radio>
428
+ ```
429
+
430
+ **Internal target** — `[data-sp-radio-native]` selects the native radio input.
431
+
432
+ ---
433
+
434
+ ### sp-label
435
+
436
+ Renders a `<label>` with `for` forwarding. Use to associate a visible label
437
+ with any form control.
438
+
439
+ **Attributes**
440
+
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>` |
445
+
446
+ **Content projection** — children become the label text (supports rich markup):
447
+
448
+ ```html
449
+ <sp-label for="email">
450
+ Email address <span aria-hidden="true">*</span>
451
+ </sp-label>
452
+ ```
220
453
 
221
- Install dependencies, then run the package checks:
454
+ **Internal target** — `[data-sp-label-native]` selects the native `<label>`.
455
+
456
+ ---
457
+
458
+ ### sp-fieldset
459
+
460
+ Renders a `<fieldset>` with optional legend and group-level state.
461
+
462
+ **Attributes**
463
+
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>` |
472
+
473
+ **Content projection** — children are placed inside the native `<fieldset>`
474
+ alongside the legend:
475
+
476
+ ```html
477
+ <sp-fieldset legend="Billing address" name="billing">
478
+ <sp-label for="city">City</sp-label>
479
+ <sp-input id="city" name="city" required></sp-input>
480
+
481
+ <sp-label for="zip">ZIP code</sp-label>
482
+ <sp-input id="zip" name="zip" type="text" maxlength="10"></sp-input>
483
+ </sp-fieldset>
484
+ ```
485
+
486
+ **Internal target** — `[data-sp-fieldset-native]` selects the native `<fieldset>`.
487
+
488
+ ## Package exports / API surface
489
+
490
+ ### Root — `@phcdevworks/spectre-components`
491
+
492
+ Exports everything from all component entry points plus the bulk registration
493
+ helper.
494
+
495
+ **Bulk registration**
496
+
497
+ ```ts
498
+ import { defineSpectreComponents } from '@phcdevworks/spectre-components';
499
+ defineSpectreComponents(); // registers all sp-* elements
500
+ ```
501
+
502
+ **Per-component helpers** (same as individual entry points):
503
+ `defineSpectreButton`, `defineSpectreInput`, `defineSpectreTextarea`,
504
+ `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`
521
+
522
+ **Props interfaces** (checkbox / radio / label / fieldset):
523
+ `SpectreCheckboxProps`, `SpectreRadioProps`, `SpectreLabelProps`,
524
+ `SpectreFieldsetProps`
525
+
526
+ ### Subpath entry points
527
+
528
+ Each entry point registers only that component and exports only its surface:
529
+
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` |
540
+
541
+ Size constants are shared between input, textarea, and select. Import
542
+ `spectreInputSizes` / `SpectreInputSize` from `.../input` when needed
543
+ alongside textarea or select.
544
+
545
+ ## Relationship to the rest of Spectre
546
+
547
+ ```
548
+ spectre-tokens → design values (colors, spacing, typography)
549
+ spectre-ui → CSS recipes and Tailwind helpers
550
+ spectre-components → Lit web component behavior ← you are here
551
+ [adapters] → React / Vue / Astro wrappers
552
+ ```
553
+
554
+ The Golden Rule: **tokens define meaning, UI defines structure, components
555
+ define behavior, adapters define delivery.** This package only owns the
556
+ behavior layer.
557
+
558
+ ## Development
222
559
 
223
560
  ```bash
224
- npm run build
225
- npm test
226
- npm run lint
561
+ git clone https://github.com/phcdevworks/spectre-components.git
562
+ cd spectre-components
563
+ npm install
564
+ npm run check # lint + typecheck + test + build + export validation
227
565
  ```
228
566
 
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/` |
578
+
229
579
  Key source areas:
230
580
 
231
- - `src/components/` for custom elements
232
- - `src/index.ts` for the root public API
233
- - `tests/` for component behavior coverage
581
+ - `src/components/` — one directory per custom element
582
+ - `src/utils/` — `base.ts`, `projectable.ts`, `form.ts`, `dom.ts`
583
+ - `src/index.ts` — root public API and bulk registration helper
584
+ - `tests/` — component behavior coverage (Vitest + happy-dom)
585
+ - `scripts/check-exports.js` — post-build export resolution check
586
+
587
+ ## Troubleshooting
588
+
589
+ **Build fails with type errors** — TypeScript 6 is required. Run
590
+ `npm install`, then `npm run build`.
591
+
592
+ **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.
594
+
595
+ **Custom element already defined** — Each `defineSpectre*()` helper is
596
+ idempotent; calling it twice is safe. If you see conflicts, two different
597
+ versions of this package may be loaded in the same page.
598
+
599
+ **Styles are not applying** — The Spectre CSS layers must load before
600
+ components are registered. Import `@phcdevworks/spectre-tokens/index.css` and
601
+ `@phcdevworks/spectre-ui/index.css` at the top of your entry module.
602
+
603
+ **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.
606
+
607
+ ## Validation
608
+
609
+ Run the full validation gate before any pull request:
610
+
611
+ ```bash
612
+ npm run check
613
+ ```
614
+
615
+ This runs: lint → typecheck → tests → build → export validation. All steps
616
+ must pass.
617
+
618
+ ## AI and automation boundaries
619
+
620
+ Claude Code (`claude-sonnet-4-6`) is the primary development agent for this
621
+ repository. Codex handles releases and production stabilization. Jules handles
622
+ small automated fixes and dependency updates. GitHub Copilot provides
623
+ development support.
624
+
625
+ Claude Code, Codex, and Copilot do not create git commits by default. Jules may
626
+ commit only bounded automated maintenance when the `JULES.md` scope and
627
+ validation gates pass. Release decisions, tags, and publishing remain with
628
+ Bradley Potts.
629
+
630
+ **Protected from automated change:** component public API surface (tags,
631
+ properties, events, slots, ARIA), the light-DOM rendering model, and the
632
+ zero-hardcode-values rule. See [AGENTS.md](AGENTS.md) for full agent governance
633
+ and boundary rules.
234
634
 
235
635
  ## Contributing
236
636
 
237
637
  PHCDevworks maintains this package as part of the Spectre suite.
238
638
 
239
- When contributing:
639
+ Contribution boundaries:
640
+
641
+ - Components must consume `@phcdevworks/spectre-ui` class helpers — do not
642
+ 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.
647
+ - Render in light DOM only — Shadow DOM changes require design-system approval.
648
+ - No framework-specific code — no JSX, SFCs, or Astro components in this
649
+ package.
650
+ - Run `npm run check` before opening a pull request.
240
651
 
241
- - treat `@phcdevworks/spectre-tokens` as the source of visual meaning
242
- - treat `@phcdevworks/spectre-ui` as the styling contract layer
243
- - avoid redefining visual primitives locally
244
- - keep components accessible by default
245
- - run `npm run build`, `npm test`, and `npm run lint` before opening a pull
246
- request
652
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.
247
653
 
248
654
  ## License
249
655