@phcdevworks/spectre-components 1.0.0 → 1.2.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 +586 -107
- package/dist/base-B_9DJkh2.d.cts +38 -0
- package/dist/base-B_9DJkh2.d.ts +38 -0
- package/dist/button.cjs +252 -90
- package/dist/button.cjs.map +1 -1
- package/dist/button.d.cts +39 -37
- package/dist/button.d.ts +39 -37
- package/dist/button.js +252 -90
- package/dist/button.js.map +1 -1
- package/dist/checkbox.cjs +228 -67
- package/dist/checkbox.cjs.map +1 -1
- package/dist/checkbox.d.cts +21 -21
- package/dist/checkbox.d.ts +21 -21
- package/dist/checkbox.js +228 -67
- package/dist/checkbox.js.map +1 -1
- package/dist/fieldset.cjs +245 -56
- package/dist/fieldset.cjs.map +1 -1
- package/dist/fieldset.d.cts +35 -14
- package/dist/fieldset.d.ts +35 -14
- package/dist/fieldset.js +245 -56
- package/dist/fieldset.js.map +1 -1
- package/dist/form-9cZG1E9P.d.cts +10 -0
- package/dist/form-9cZG1E9P.d.ts +10 -0
- package/dist/index.cjs +647 -328
- 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 +647 -328
- package/dist/index.js.map +1 -1
- package/dist/input.cjs +262 -77
- package/dist/input.cjs.map +1 -1
- package/dist/input.d.cts +72 -40
- package/dist/input.d.ts +72 -40
- package/dist/input.js +262 -77
- package/dist/input.js.map +1 -1
- package/dist/label.cjs +217 -55
- package/dist/label.cjs.map +1 -1
- package/dist/label.d.cts +17 -10
- package/dist/label.d.ts +17 -10
- package/dist/label.js +217 -55
- package/dist/label.js.map +1 -1
- package/dist/{projectable-DcnBRiko.d.cts → projectable-CIfxi7dv.d.cts} +1 -1
- package/dist/{projectable-SzbPvXfM.d.ts → projectable-Ksv9eoIr.d.ts} +1 -1
- package/dist/radio.cjs +248 -67
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.d.cts +23 -21
- package/dist/radio.d.ts +23 -21
- package/dist/radio.js +248 -67
- package/dist/radio.js.map +1 -1
- package/dist/select.cjs +230 -67
- package/dist/select.cjs.map +1 -1
- package/dist/select.d.cts +27 -27
- package/dist/select.d.ts +27 -27
- package/dist/select.js +230 -67
- package/dist/select.js.map +1 -1
- package/dist/textarea.cjs +256 -77
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.d.cts +53 -32
- package/dist/textarea.d.ts +53 -32
- package/dist/textarea.js +256 -77
- package/dist/textarea.js.map +1 -1
- package/package.json +22 -12
- package/dist/base-DQtCQ-dP.d.cts +0 -22
- package/dist/base-DQtCQ-dP.d.ts +0 -22
- 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,147 +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.
|
|
39
86
|
|
|
40
|
-
|
|
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
|
+
```
|
|
41
109
|
|
|
110
|
+
### JavaScript / TypeScript module
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import '@phcdevworks/spectre-tokens/index.css';
|
|
114
|
+
import '@phcdevworks/spectre-ui/index.css';
|
|
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-
|
|
49
|
-
<sp-
|
|
50
|
-
<sp-input
|
|
130
|
+
<sp-fieldset legend="Contact preferences">
|
|
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>
|
|
152
|
+
</sp-fieldset>
|
|
51
153
|
```
|
|
52
154
|
|
|
53
|
-
|
|
155
|
+
### Framework integration note
|
|
54
156
|
|
|
55
|
-
|
|
56
|
-
|
|
157
|
+
These are standard HTML custom elements. They work in every major framework
|
|
158
|
+
that supports the Custom Elements standard:
|
|
57
159
|
|
|
58
|
-
|
|
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)} />
|
|
59
164
|
```
|
|
60
165
|
|
|
61
|
-
|
|
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
|
+
```
|
|
62
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:
|
|
63
179
|
```ts
|
|
64
|
-
|
|
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" />
|
|
185
|
+
```
|
|
65
186
|
|
|
66
|
-
|
|
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>
|
|
67
199
|
```
|
|
68
200
|
|
|
69
|
-
|
|
201
|
+
> Framework adapter packages that wrap these components into idiomatic JSX or
|
|
202
|
+
> SFC APIs belong in a downstream adapter — not in this package.
|
|
70
203
|
|
|
71
|
-
|
|
72
|
-
- Accessible component behavior and DOM structure
|
|
73
|
-
- Public custom element registration helpers
|
|
74
|
-
- Component-level TypeScript APIs for future adapters to build on
|
|
204
|
+
## Accessibility
|
|
75
205
|
|
|
76
|
-
|
|
77
|
-
contracts locally.
|
|
206
|
+
All components follow WCAG 2.1 AA baseline expectations by default.
|
|
78
207
|
|
|
79
|
-
|
|
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.
|
|
80
211
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
[`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
|
|
85
|
-
- Framework adapters such as React, Vue, Astro, or app-specific wrappers Those
|
|
86
|
-
belong in downstream adapter packages.
|
|
87
|
-
- Routing, shell logic, manifest behavior, or startup orchestration Those are
|
|
88
|
-
outside the scope of this package.
|
|
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.
|
|
89
215
|
|
|
90
|
-
|
|
216
|
+
**State communication**
|
|
91
217
|
|
|
92
|
-
|
|
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 |
|
|
93
224
|
|
|
94
|
-
|
|
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.
|
|
95
228
|
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
|
|
99
|
-
- `SpectreButtonElement`
|
|
100
|
-
- `SpectreInputElement`
|
|
101
|
-
- `spectreButtonVariants`
|
|
102
|
-
- `spectreButtonSizes`
|
|
103
|
-
- `spectreButtonTypes`
|
|
104
|
-
- `spectreInputTypes`
|
|
105
|
-
- `spectreInputSizes`
|
|
106
|
-
- `SpectreButtonProps` and related button types
|
|
107
|
-
- `SpectreInputProps` and related input types
|
|
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.
|
|
108
232
|
|
|
109
|
-
|
|
233
|
+
**Keyboard behavior** — provided entirely by the native element inside each
|
|
234
|
+
component. No custom keyboard handling is layered on top.
|
|
110
235
|
|
|
111
|
-
|
|
112
|
-
consumers can register a single component without importing the full package
|
|
113
|
-
entry.
|
|
236
|
+
## Light DOM rendering
|
|
114
237
|
|
|
115
|
-
|
|
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.
|
|
116
241
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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:
|
|
120
245
|
|
|
121
|
-
|
|
246
|
+
```css
|
|
247
|
+
/* Target the native input inside sp-input */
|
|
248
|
+
sp-input input { font-size: 0.875rem; }
|
|
122
249
|
|
|
123
|
-
|
|
250
|
+
/* Stable internal hook — won't break if markup restructures */
|
|
251
|
+
sp-input [data-sp-input-native] { font-size: 0.875rem; }
|
|
252
|
+
```
|
|
124
253
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui)
|
|
128
|
-
provides CSS, Tailwind helpers, recipes, and styling contracts
|
|
129
|
-
- `@phcdevworks/spectre-components` turns those layers into Lit-based reusable
|
|
130
|
-
web components
|
|
254
|
+
Do not switch any component from light DOM to Shadow DOM without a
|
|
255
|
+
design-system-level decision.
|
|
131
256
|
|
|
132
|
-
|
|
133
|
-
component behavior reusable across frameworks.
|
|
257
|
+
## Components
|
|
134
258
|
|
|
135
|
-
|
|
259
|
+
### sp-button
|
|
136
260
|
|
|
137
|
-
|
|
261
|
+
Renders a `<button>` with Spectre variant, size, loading, and pill support.
|
|
138
262
|
|
|
139
|
-
|
|
140
|
-
- root and subpath exports
|
|
141
|
-
- a Lit-based `sp-button` starter component
|
|
142
|
-
- a Lit-based `sp-input` foundation component
|
|
143
|
-
- explicit registration helpers instead of implicit global side effects
|
|
144
|
-
- Vitest coverage for baseline rendering and accessibility behavior
|
|
263
|
+
**Attributes**
|
|
145
264
|
|
|
146
|
-
|
|
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:
|
|
290
|
+
|
|
291
|
+
```html
|
|
292
|
+
<sp-button variant="primary">
|
|
293
|
+
<svg aria-hidden="true">...</svg>
|
|
294
|
+
Save changes
|
|
295
|
+
</sp-button>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Internal target** — `[data-sp-button-native]` selects the native `<button>`.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### sp-input
|
|
303
|
+
|
|
304
|
+
Renders an `<input>` with state, size, and type support.
|
|
305
|
+
|
|
306
|
+
**Attributes**
|
|
307
|
+
|
|
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>` |
|
|
330
|
+
|
|
331
|
+
**Events** — `input` and `change` fire from the native `<input>` and bubble.
|
|
332
|
+
|
|
333
|
+
**Internal target** — `[data-sp-input-native]` selects the native `<input>`.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### sp-textarea
|
|
338
|
+
|
|
339
|
+
Renders a `<textarea>` with row control and resize support.
|
|
340
|
+
|
|
341
|
+
**Attributes** — same as `sp-input` except no `type`, `min`, `max`, `step`, and adds:
|
|
342
|
+
|
|
343
|
+
| Attribute | Type | Default | Description |
|
|
344
|
+
|-----------|------|---------|-------------|
|
|
345
|
+
| `rows` | number | `2` | Visible row height |
|
|
346
|
+
|
|
347
|
+
**Events** — `input` and `change` fire from the native `<textarea>`.
|
|
348
|
+
|
|
349
|
+
**Internal target** — `[data-sp-textarea-native]` selects the native `<textarea>`.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
### sp-select
|
|
354
|
+
|
|
355
|
+
Renders a `<select>`. Pass `<option>` elements as children — they are
|
|
356
|
+
projected into the native select element.
|
|
357
|
+
|
|
358
|
+
**Attributes** — same as `sp-input` minus `type`, `placeholder`, `readonly`,
|
|
359
|
+
`inputmode`, `min`, `max`, `step`, `minlength`, `maxlength`.
|
|
360
|
+
|
|
361
|
+
**Events** — `input` and `change` fire from the native `<select>`.
|
|
362
|
+
|
|
363
|
+
**Content projection** — `<option>` and `<optgroup>` children are moved into
|
|
364
|
+
the native `<select>`:
|
|
365
|
+
|
|
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
|
+
```
|
|
375
|
+
|
|
376
|
+
**Internal target** — `[data-sp-select-native]` selects the native `<select>`.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### sp-checkbox
|
|
381
|
+
|
|
382
|
+
Renders a `<label>` wrapping an `<input type="checkbox">` with indicator.
|
|
383
|
+
|
|
384
|
+
**Attributes**
|
|
385
|
+
|
|
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>` |
|
|
398
|
+
|
|
399
|
+
**Events** — `input` and `change` fire from the native checkbox input.
|
|
400
|
+
|
|
401
|
+
**Content projection** — children become the label content (supports rich
|
|
402
|
+
markup):
|
|
403
|
+
|
|
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
|
+
```
|
|
147
409
|
|
|
148
|
-
|
|
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
|
+
```
|
|
453
|
+
|
|
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
|
|
149
559
|
|
|
150
560
|
```bash
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
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
|
|
154
565
|
```
|
|
155
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
|
+
|
|
156
579
|
Key source areas:
|
|
157
580
|
|
|
158
|
-
- `src/components/`
|
|
159
|
-
- `src/
|
|
160
|
-
- `
|
|
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.
|
|
161
634
|
|
|
162
635
|
## Contributing
|
|
163
636
|
|
|
164
637
|
PHCDevworks maintains this package as part of the Spectre suite.
|
|
165
638
|
|
|
166
|
-
|
|
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.
|
|
167
651
|
|
|
168
|
-
|
|
169
|
-
- treat `@phcdevworks/spectre-ui` as the styling contract layer
|
|
170
|
-
- avoid redefining visual primitives locally
|
|
171
|
-
- keep components accessible by default
|
|
172
|
-
- run `npm run build`, `npm test`, and `npm run lint` before opening a pull
|
|
173
|
-
request
|
|
652
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.
|
|
174
653
|
|
|
175
654
|
## License
|
|
176
655
|
|