@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.
- package/README.md +559 -153
- package/dist/{base-2rD8EmdC.d.cts → base-BRQ7QAAr.d.cts} +9 -0
- package/dist/{base-2rD8EmdC.d.ts → base-BRQ7QAAr.d.ts} +9 -0
- package/dist/button.cjs +146 -35
- package/dist/button.cjs.map +1 -1
- package/dist/button.d.cts +34 -29
- package/dist/button.d.ts +34 -29
- package/dist/button.js +146 -35
- package/dist/button.js.map +1 -1
- package/dist/checkbox.cjs +113 -6
- package/dist/checkbox.cjs.map +1 -1
- package/dist/checkbox.d.cts +21 -19
- package/dist/checkbox.d.ts +21 -19
- package/dist/checkbox.js +113 -6
- package/dist/checkbox.js.map +1 -1
- package/dist/fieldset.cjs +126 -5
- package/dist/fieldset.cjs.map +1 -1
- package/dist/fieldset.d.cts +36 -7
- package/dist/fieldset.d.ts +36 -7
- package/dist/fieldset.js +126 -5
- package/dist/fieldset.js.map +1 -1
- package/dist/form-yR_u97J-.d.cts +10 -0
- package/dist/form-yR_u97J-.d.ts +10 -0
- package/dist/index.cjs +530 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +530 -96
- package/dist/index.js.map +1 -1
- package/dist/input.cjs +138 -7
- package/dist/input.cjs.map +1 -1
- package/dist/input.d.cts +49 -23
- package/dist/input.d.ts +49 -23
- package/dist/input.js +138 -7
- package/dist/input.js.map +1 -1
- package/dist/label.cjs +98 -3
- package/dist/label.cjs.map +1 -1
- package/dist/label.d.cts +19 -6
- package/dist/label.d.ts +19 -6
- package/dist/label.js +98 -3
- package/dist/label.js.map +1 -1
- package/dist/{projectable-BzDQ4xp_.d.cts → projectable-Bx-IVk0w.d.cts} +1 -1
- package/dist/{projectable-s3SgvjGX.d.ts → projectable-D6IDeKfr.d.ts} +1 -1
- package/dist/radio.cjs +133 -6
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.d.cts +23 -19
- package/dist/radio.d.ts +23 -19
- package/dist/radio.js +133 -6
- package/dist/radio.js.map +1 -1
- package/dist/select.cjs +120 -8
- package/dist/select.cjs.map +1 -1
- package/dist/select.d.cts +24 -22
- package/dist/select.d.ts +24 -22
- package/dist/select.js +120 -8
- package/dist/select.js.map +1 -1
- package/dist/textarea.cjs +134 -7
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.d.cts +37 -22
- package/dist/textarea.d.ts +37 -22
- package/dist/textarea.js +134 -7
- package/dist/textarea.js.map +1 -1
- package/package.json +24 -12
- package/dist/form-iI7vV-3W.d.cts +0 -6
- 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
|
-
[](https://www.npmjs.com/package/@phcdevworks/spectre-components)
|
|
4
|
+
[](https://github.com/phcdevworks/spectre-components/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](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
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
-
|
|
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
|
-
|
|
34
|
-
to use:
|
|
81
|
+
### Plain HTML
|
|
35
82
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
contracts locally.
|
|
204
|
+
## Accessibility
|
|
86
205
|
|
|
87
|
-
|
|
206
|
+
All components follow WCAG 2.1 AA baseline expectations by default.
|
|
88
207
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
216
|
+
**State communication**
|
|
101
217
|
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
108
|
-
-
|
|
109
|
-
|
|
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
|
-
**
|
|
233
|
+
**Keyboard behavior** — provided entirely by the native element inside each
|
|
234
|
+
component. No custom keyboard handling is layered on top.
|
|
113
235
|
|
|
114
|
-
|
|
115
|
-
- `SpectreSelectElement`, `SpectreCheckboxElement`, `SpectreRadioElement`
|
|
116
|
-
- `SpectreLabelElement`, `SpectreFieldsetElement`
|
|
236
|
+
## Light DOM rendering
|
|
117
237
|
|
|
118
|
-
**
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
246
|
+
```css
|
|
247
|
+
/* Target the native input inside sp-input */
|
|
248
|
+
sp-input input { font-size: 0.875rem; }
|
|
125
249
|
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
|
|
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
|
-
|
|
254
|
+
Do not switch any component from light DOM to Shadow DOM without a
|
|
255
|
+
design-system-level decision.
|
|
131
256
|
|
|
132
|
-
|
|
133
|
-
- `SpectreLabelProps`, `SpectreFieldsetProps`
|
|
257
|
+
## Components
|
|
134
258
|
|
|
135
|
-
###
|
|
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
|
-
|
|
291
|
+
```html
|
|
292
|
+
<sp-button variant="primary">
|
|
293
|
+
<svg aria-hidden="true">...</svg>
|
|
294
|
+
Save changes
|
|
295
|
+
</sp-button>
|
|
296
|
+
```
|
|
138
297
|
|
|
139
|
-
|
|
140
|
-
`spectreButtonVariants`, `spectreButtonSizes`, `spectreButtonTypes`,
|
|
141
|
-
`SpectreButtonVariant`, `SpectreButtonSize`, `SpectreButtonType`.
|
|
298
|
+
**Internal target** — `[data-sp-button-native]` selects the native `<button>`.
|
|
142
299
|
|
|
143
|
-
|
|
300
|
+
---
|
|
144
301
|
|
|
145
|
-
|
|
302
|
+
### sp-input
|
|
146
303
|
|
|
147
|
-
|
|
148
|
-
`spectreInputSizes`, `spectreInputTypes`, `SpectreInputSize`, `SpectreInputType`.
|
|
304
|
+
Renders an `<input>` with state, size, and type support.
|
|
149
305
|
|
|
150
|
-
|
|
306
|
+
**Attributes**
|
|
151
307
|
|
|
152
|
-
|
|
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
|
-
|
|
331
|
+
**Events** — `input` and `change` fire from the native `<input>` and bubble.
|
|
155
332
|
|
|
156
|
-
|
|
157
|
-
from `@phcdevworks/spectre-components/input`.
|
|
333
|
+
**Internal target** — `[data-sp-input-native]` selects the native `<input>`.
|
|
158
334
|
|
|
159
|
-
|
|
335
|
+
---
|
|
160
336
|
|
|
161
|
-
|
|
337
|
+
### sp-textarea
|
|
162
338
|
|
|
163
|
-
|
|
339
|
+
Renders a `<textarea>` with row control and resize support.
|
|
164
340
|
|
|
165
|
-
|
|
166
|
-
from `@phcdevworks/spectre-components/input`.
|
|
341
|
+
**Attributes** — same as `sp-input` except no `type`, `min`, `max`, `step`, and adds:
|
|
167
342
|
|
|
168
|
-
|
|
343
|
+
| Attribute | Type | Default | Description |
|
|
344
|
+
|-----------|------|---------|-------------|
|
|
345
|
+
| `rows` | number | `2` | Visible row height |
|
|
169
346
|
|
|
170
|
-
|
|
347
|
+
**Events** — `input` and `change` fire from the native `<textarea>`.
|
|
171
348
|
|
|
172
|
-
|
|
349
|
+
**Internal target** — `[data-sp-textarea-native]` selects the native `<textarea>`.
|
|
173
350
|
|
|
174
|
-
|
|
351
|
+
---
|
|
175
352
|
|
|
176
|
-
|
|
353
|
+
### sp-select
|
|
177
354
|
|
|
178
|
-
|
|
355
|
+
Renders a `<select>`. Pass `<option>` elements as children — they are
|
|
356
|
+
projected into the native select element.
|
|
179
357
|
|
|
180
|
-
|
|
358
|
+
**Attributes** — same as `sp-input` minus `type`, `placeholder`, `readonly`,
|
|
359
|
+
`inputmode`, `min`, `max`, `step`, `minlength`, `maxlength`.
|
|
181
360
|
|
|
182
|
-
|
|
361
|
+
**Events** — `input` and `change` fire from the native `<select>`.
|
|
183
362
|
|
|
184
|
-
|
|
363
|
+
**Content projection** — `<option>` and `<optgroup>` children are moved into
|
|
364
|
+
the native `<select>`:
|
|
185
365
|
|
|
186
|
-
|
|
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
|
-
|
|
376
|
+
**Internal target** — `[data-sp-select-native]` selects the native `<select>`.
|
|
189
377
|
|
|
190
|
-
|
|
378
|
+
---
|
|
191
379
|
|
|
192
|
-
|
|
380
|
+
### sp-checkbox
|
|
193
381
|
|
|
194
|
-
|
|
382
|
+
Renders a `<label>` wrapping an `<input type="checkbox">` with indicator.
|
|
195
383
|
|
|
196
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
399
|
+
**Events** — `input` and `change` fire from the native checkbox input.
|
|
207
400
|
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
npm
|
|
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/`
|
|
232
|
-
- `src/
|
|
233
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|