@neuravision/construct 2.0.2 → 2.1.1
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/CHANGELOG.md +272 -0
- package/README.md +148 -152
- package/behaviors/combobox.js +447 -0
- package/behaviors/drawer.js +18 -0
- package/behaviors/dropdown.js +296 -0
- package/behaviors/index.d.ts +333 -0
- package/behaviors/index.js +9 -0
- package/behaviors/internal/dialog-controller.js +351 -0
- package/behaviors/internal/dom.js +377 -0
- package/behaviors/internal/overlay.js +241 -0
- package/behaviors/internal/popup-layer.js +188 -0
- package/behaviors/modal.js +17 -0
- package/behaviors/popover.js +269 -0
- package/behaviors/select-menu.js +326 -0
- package/behaviors/tabs.js +230 -0
- package/behaviors/toggle-group.js +234 -0
- package/behaviors/tooltip.js +233 -0
- package/components/README.md +279 -83
- package/components/_shared.css +11 -10
- package/components/_shell-shared.css +20 -4
- package/components/app-shell-v2.css +21 -14
- package/components/app-shell.css +23 -14
- package/components/badge.css +15 -0
- package/components/button.css +103 -15
- package/components/card.css +53 -8
- package/components/checkbox.css +3 -2
- package/components/chip.css +1 -1
- package/components/datepicker.css +3 -1
- package/components/datum.css +204 -0
- package/components/index.css +5 -0
- package/components/meter.css +182 -0
- package/components/navbar.css +88 -3
- package/components/slider.css +2 -1
- package/components/status.css +107 -0
- package/components/switch.css +4 -3
- package/components/tooltip.css +4 -0
- package/docs/architecture.md +79 -0
- package/docs/behaviors.md +101 -0
- package/docs/fonts.md +70 -0
- package/docs/governance.md +85 -0
- package/docs/guidelines.md +42 -23
- package/examples/react/README.md +20 -0
- package/examples/react/SettingsModal.jsx +66 -0
- package/examples/react/construct.css +5 -0
- package/fonts.css +10 -0
- package/foundations.css +8 -1
- package/package.json +72 -21
- package/schemas/components.schema.json +107 -0
- package/schemas/examples/theme.example.json +27 -0
- package/schemas/primitives.schema.json +135 -0
- package/schemas/semantic.schema.json +223 -0
- package/schemas/theme.schema.json +63 -0
- package/scripts/build-tokens.mjs +1417 -0
- package/scripts/construct-theme.mjs +43 -0
- package/scripts/validate-tokens.mjs +25 -0
- package/standalone/accordion.css +3 -0
- package/standalone/alert.css +3 -0
- package/standalone/app-shell-v2.css +4 -0
- package/standalone/app-shell.css +4 -0
- package/standalone/avatar.css +3 -0
- package/standalone/badge.css +3 -0
- package/standalone/banner.css +3 -0
- package/standalone/breadcrumbs.css +3 -0
- package/standalone/button.css +3 -0
- package/standalone/card.css +3 -0
- package/standalone/chart.css +3 -0
- package/standalone/checkbox.css +3 -0
- package/standalone/chip.css +3 -0
- package/standalone/combobox.css +3 -0
- package/standalone/core.css +4 -0
- package/standalone/data-table.css +3 -0
- package/standalone/datepicker.css +3 -0
- package/standalone/datum.css +3 -0
- package/standalone/divider.css +3 -0
- package/standalone/drawer.css +3 -0
- package/standalone/dropdown.css +3 -0
- package/standalone/empty-state.css +3 -0
- package/standalone/field.css +3 -0
- package/standalone/file-upload.css +3 -0
- package/standalone/icon.css +3 -0
- package/standalone/input.css +3 -0
- package/standalone/list.css +3 -0
- package/standalone/meter.css +3 -0
- package/standalone/modal.css +3 -0
- package/standalone/navbar.css +3 -0
- package/standalone/pagination.css +3 -0
- package/standalone/popover.css +3 -0
- package/standalone/progress-bar.css +3 -0
- package/standalone/select-menu.css +3 -0
- package/standalone/select.css +3 -0
- package/standalone/sidebar.css +3 -0
- package/standalone/skeleton.css +3 -0
- package/standalone/skip-link.css +3 -0
- package/standalone/slider.css +3 -0
- package/standalone/spinner.css +3 -0
- package/standalone/status.css +3 -0
- package/standalone/switch.css +3 -0
- package/standalone/table.css +3 -0
- package/standalone/tabs.css +3 -0
- package/standalone/textarea.css +3 -0
- package/standalone/toast.css +3 -0
- package/standalone/toggle-group.css +3 -0
- package/standalone/toolbar.css +3 -0
- package/standalone/tooltip.css +3 -0
- package/standalone/tree.css +3 -0
- package/tokens/README.md +130 -126
- package/tokens/components.json +54 -0
- package/tokens/primitives.json +268 -0
- package/tokens/semantic.dark.json +93 -0
- package/tokens/semantic.high-contrast.json +93 -0
- package/tokens/semantic.light.json +178 -0
- package/tokens/tokens.css +532 -28
- package/tokens/tokens.js +335 -25
- package/tokens/tokens.json +764 -21
- package/tokens/tokens.ts +335 -25
package/components/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Construct Components
|
|
1
|
+
# Construct Components v2
|
|
2
2
|
|
|
3
|
-
Framework-agnostic component styles built on Construct design tokens. Use these styles directly with HTML or wrap them in Angular, React, or Svelte components.
|
|
3
|
+
Framework-agnostic component styles built on Construct design tokens. Use these styles directly with HTML or wrap them in Angular, React, or Svelte components. Visual state follows native, ARIA, and documented data attributes; optional typed controllers provide the corresponding keyboard and focus behavior.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -18,18 +18,26 @@ Example:
|
|
|
18
18
|
## State Conventions
|
|
19
19
|
|
|
20
20
|
- `data-state="open"` / `data-state="closed"` for popovers, modals, tooltips
|
|
21
|
+
- `data-state="active"`, `loading`, `error`, or `disabled` for component state
|
|
22
|
+
- `data-status="success"`, `info`, `warning`, `danger`, or connection-specific status text for semantic status
|
|
23
|
+
- `aria-busy="true"` while a region is updating; keep a visible loading message
|
|
24
|
+
- `data-loading="true"` as the button's visual loading hook; native buttons also require `disabled`
|
|
21
25
|
- `aria-invalid="true"` for form errors
|
|
22
26
|
- `aria-current="page"` for current pagination page
|
|
23
27
|
- `aria-selected="true"` for active tabs and selected datepicker days
|
|
24
28
|
- `aria-disabled="true"` when disabled but not using native `disabled`
|
|
25
29
|
|
|
30
|
+
ARIA communicates state but does not block events. Prefer native `disabled` for form controls. For links and custom controls, suppress pointer and keyboard activation in application code when `aria-disabled="true"` is set. Add live-region attributes only to content that changes dynamically; static status labels do not need `aria-live`.
|
|
31
|
+
|
|
26
32
|
## Keyboard + ARIA Requirements
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
Use `@neuravision/construct/behaviors` where a controller is available (including Tabs, menu Dropdown,
|
|
35
|
+
Modal, and Tooltip), or implement an equivalent tested framework adapter. CSS alone does not provide
|
|
36
|
+
these keyboard patterns:
|
|
29
37
|
|
|
30
38
|
- **Tabs**: Arrow keys move focus between tabs, Home/End jumps to first/last, Enter/Space activates. Use roving tabindex (active tab `tabindex="0"`, others `tabindex="-1"`).
|
|
31
|
-
- **Dropdown (Action
|
|
32
|
-
- **
|
|
39
|
+
- **Dropdown (Action menu)**: Trigger uses `aria-expanded`, `aria-controls`, and `aria-haspopup="menu"`. The menu uses roving focus; Arrow keys, Home/End, and typeahead navigate, Esc closes and restores trigger focus, and Tab closes without cancelling normal focus movement. Use `createDropdownController` for this complete contract.
|
|
40
|
+
- **Generic disclosure**: When popup content contains ordinary links or controls in natural Tab order, use the Popover pattern rather than adding menu roles.
|
|
33
41
|
- **Datepicker**: Arrow keys move by day, PageUp/PageDown switches months, Home/End jumps within week, Enter/Space selects, Esc closes.
|
|
34
42
|
- **Modal**: Focus trap, initial focus inside dialog, Esc closes, focus returns to trigger.
|
|
35
43
|
- **Tooltip**: Opens on hover/focus, closes on blur/Esc, uses `role="tooltip"` with `aria-describedby`.
|
|
@@ -41,31 +49,39 @@ When building framework wrappers, implement these keyboard patterns:
|
|
|
41
49
|
Basic button with variants and sizes:
|
|
42
50
|
|
|
43
51
|
```html
|
|
44
|
-
<button class="ct-button">Primary</button>
|
|
45
|
-
<button class="ct-button ct-button--secondary">Secondary</button>
|
|
46
|
-
<button class="ct-button ct-button--ghost">Ghost</button>
|
|
47
|
-
<button class="ct-button ct-button--outline">Outline</button>
|
|
48
|
-
<button class="ct-button ct-button--danger">Danger</button>
|
|
49
|
-
<button class="ct-button ct-button--accent">Accent</button>
|
|
50
|
-
<button class="ct-button ct-button--link">Link</button>
|
|
52
|
+
<button class="ct-button" type="button">Primary</button>
|
|
53
|
+
<button class="ct-button ct-button--secondary" type="button">Secondary</button>
|
|
54
|
+
<button class="ct-button ct-button--ghost" type="button">Ghost</button>
|
|
55
|
+
<button class="ct-button ct-button--outline" type="button">Outline</button>
|
|
56
|
+
<button class="ct-button ct-button--danger" type="button">Danger</button>
|
|
57
|
+
<button class="ct-button ct-button--accent" type="button">Accent</button>
|
|
58
|
+
<button class="ct-button ct-button--link" type="button">Link</button>
|
|
51
59
|
|
|
52
60
|
<!-- Sizes -->
|
|
53
|
-
<button class="ct-button ct-button--sm">Small</button>
|
|
54
|
-
<button class="ct-button">Medium</button>
|
|
55
|
-
<button class="ct-button ct-button--lg">Large</button>
|
|
61
|
+
<button class="ct-button ct-button--sm" type="button">Small</button>
|
|
62
|
+
<button class="ct-button" type="button">Medium</button>
|
|
63
|
+
<button class="ct-button ct-button--lg" type="button">Large</button>
|
|
56
64
|
|
|
57
65
|
<!-- With icon -->
|
|
58
|
-
<button class="ct-button">
|
|
66
|
+
<button class="ct-button" type="button">
|
|
59
67
|
<span class="ct-button__icon" aria-hidden="true">+</span>
|
|
60
68
|
Add item
|
|
61
69
|
</button>
|
|
62
70
|
|
|
63
71
|
<!-- Icon only -->
|
|
64
|
-
<button class="ct-button ct-button--icon" aria-label="Settings">
|
|
72
|
+
<button class="ct-button ct-button--icon" type="button" aria-label="Settings">
|
|
65
73
|
<span class="ct-button__icon" aria-hidden="true">⚙</span>
|
|
66
74
|
</button>
|
|
75
|
+
|
|
76
|
+
<!-- Loading: stable label, decorative loader, native interaction lock -->
|
|
77
|
+
<button class="ct-button" type="button" aria-busy="true" data-loading="true" disabled>
|
|
78
|
+
<span class="ct-button__content">Save changes</span>
|
|
79
|
+
<span class="ct-button__loader" aria-hidden="true"></span>
|
|
80
|
+
</button>
|
|
67
81
|
```
|
|
68
82
|
|
|
83
|
+
The `.ct-button__content` remains in layout, so switching to loading does not change the button width. Its text also remains the accessible name. `aria-busy` exposes the state, `data-loading` selects the visual treatment, and native `disabled` prevents both pointer and keyboard activation. The spinner stops animating under `prefers-reduced-motion: reduce`.
|
|
84
|
+
|
|
69
85
|
### Field + Input
|
|
70
86
|
|
|
71
87
|
Form field with label, input, hint, and error states:
|
|
@@ -73,15 +89,17 @@ Form field with label, input, hint, and error states:
|
|
|
73
89
|
```html
|
|
74
90
|
<div class="ct-field">
|
|
75
91
|
<label class="ct-field__label" for="email">Email</label>
|
|
76
|
-
<input class="ct-input" id="email" type="email" placeholder="name@company.com"
|
|
77
|
-
|
|
92
|
+
<input class="ct-input" id="email" type="email" placeholder="name@company.com"
|
|
93
|
+
aria-describedby="email-hint" />
|
|
94
|
+
<div class="ct-field__hint" id="email-hint">We will not share this.</div>
|
|
78
95
|
</div>
|
|
79
96
|
|
|
80
97
|
<!-- Error state -->
|
|
81
98
|
<div class="ct-field ct-field--error">
|
|
82
99
|
<label class="ct-field__label" for="name">Name</label>
|
|
83
|
-
<input class="ct-input" id="name" aria-invalid="true"
|
|
84
|
-
|
|
100
|
+
<input class="ct-input" id="name" type="text" aria-invalid="true"
|
|
101
|
+
aria-describedby="name-error" />
|
|
102
|
+
<div class="ct-field__error" id="name-error">Name is required.</div>
|
|
85
103
|
</div>
|
|
86
104
|
|
|
87
105
|
<!-- With icon -->
|
|
@@ -122,7 +140,7 @@ Multi-line text input:
|
|
|
122
140
|
|
|
123
141
|
### Checkbox
|
|
124
142
|
|
|
125
|
-
Single checkbox or checkbox group:
|
|
143
|
+
Single checkbox or a labelled checkbox group:
|
|
126
144
|
|
|
127
145
|
```html
|
|
128
146
|
<label class="ct-check">
|
|
@@ -130,10 +148,17 @@ Single checkbox or checkbox group:
|
|
|
130
148
|
<span class="ct-check__label">Remember me</span>
|
|
131
149
|
</label>
|
|
132
150
|
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
<
|
|
136
|
-
|
|
151
|
+
<fieldset>
|
|
152
|
+
<legend class="ct-field__label">Notifications</legend>
|
|
153
|
+
<label class="ct-check">
|
|
154
|
+
<input class="ct-check__input" type="checkbox" checked />
|
|
155
|
+
<span class="ct-check__label">Send weekly reports</span>
|
|
156
|
+
</label>
|
|
157
|
+
<label class="ct-check">
|
|
158
|
+
<input class="ct-check__input" type="checkbox" />
|
|
159
|
+
<span class="ct-check__label">Send security alerts</span>
|
|
160
|
+
</label>
|
|
161
|
+
</fieldset>
|
|
137
162
|
```
|
|
138
163
|
|
|
139
164
|
### Radio
|
|
@@ -141,15 +166,18 @@ Single checkbox or checkbox group:
|
|
|
141
166
|
Radio button group:
|
|
142
167
|
|
|
143
168
|
```html
|
|
144
|
-
<
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
169
|
+
<fieldset>
|
|
170
|
+
<legend class="ct-field__label">Plan</legend>
|
|
171
|
+
<label class="ct-radio">
|
|
172
|
+
<input class="ct-radio__input" type="radio" name="plan" checked />
|
|
173
|
+
<span class="ct-radio__label">Standard</span>
|
|
174
|
+
</label>
|
|
148
175
|
|
|
149
|
-
<label class="ct-radio">
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
</label>
|
|
176
|
+
<label class="ct-radio">
|
|
177
|
+
<input class="ct-radio__input" type="radio" name="plan" />
|
|
178
|
+
<span class="ct-radio__label">Premium</span>
|
|
179
|
+
</label>
|
|
180
|
+
</fieldset>
|
|
153
181
|
```
|
|
154
182
|
|
|
155
183
|
### Switch
|
|
@@ -171,7 +199,7 @@ Content card with header, body, and footer:
|
|
|
171
199
|
<section class="ct-card">
|
|
172
200
|
<div class="ct-card__header">
|
|
173
201
|
<h3>Team</h3>
|
|
174
|
-
<button class="ct-button ct-button--ghost">Edit</button>
|
|
202
|
+
<button class="ct-button ct-button--ghost" type="button">Edit</button>
|
|
175
203
|
</div>
|
|
176
204
|
<div class="ct-card__body">
|
|
177
205
|
<p>Shared ownership and clear permissions.</p>
|
|
@@ -179,7 +207,7 @@ Content card with header, body, and footer:
|
|
|
179
207
|
</div>
|
|
180
208
|
<div class="ct-card__footer">
|
|
181
209
|
<span class="ct-muted">12 members</span>
|
|
182
|
-
<button class="ct-button ct-button--secondary">Open</button>
|
|
210
|
+
<button class="ct-button ct-button--secondary" type="button">Open</button>
|
|
183
211
|
</div>
|
|
184
212
|
</section>
|
|
185
213
|
```
|
|
@@ -217,7 +245,8 @@ Basic data table with variants:
|
|
|
217
245
|
Variants:
|
|
218
246
|
- `ct-table--striped` - Alternating row backgrounds
|
|
219
247
|
- `ct-table--compact` - Reduced padding
|
|
220
|
-
|
|
248
|
+
|
|
249
|
+
Rows receive a visual hover affordance automatically on hover-capable pointers; no hover modifier exists.
|
|
221
250
|
|
|
222
251
|
### Data Table
|
|
223
252
|
|
|
@@ -231,8 +260,8 @@ Complex data table with header, toolbar, filters, and actions:
|
|
|
231
260
|
<span class="ct-data-table__meta">24 total</span>
|
|
232
261
|
</div>
|
|
233
262
|
<div class="ct-data-table__actions">
|
|
234
|
-
<button class="ct-button ct-button--secondary ct-button--sm">Export</button>
|
|
235
|
-
<button class="ct-button ct-button--sm">New project</button>
|
|
263
|
+
<button class="ct-button ct-button--secondary ct-button--sm" type="button">Export</button>
|
|
264
|
+
<button class="ct-button ct-button--sm" type="button">New project</button>
|
|
236
265
|
</div>
|
|
237
266
|
</div>
|
|
238
267
|
|
|
@@ -260,29 +289,89 @@ Complex data table with header, toolbar, filters, and actions:
|
|
|
260
289
|
</div>
|
|
261
290
|
```
|
|
262
291
|
|
|
292
|
+
### Metric / Stat
|
|
293
|
+
|
|
294
|
+
Use `ct-metric` for product metrics. It is the semantic successor to `ct-spec`; the existing `ct-spec` classes remain supported for compatibility. Values use tabular numerals, and every semantic status includes visible text.
|
|
295
|
+
|
|
296
|
+
```html
|
|
297
|
+
<dl class="ct-metrics" aria-label="Service metrics">
|
|
298
|
+
<div class="ct-metric" data-status="success">
|
|
299
|
+
<dt class="ct-metric__label">Availability</dt>
|
|
300
|
+
<dd class="ct-metric__value">99.98<small>%</small>
|
|
301
|
+
<span class="ct-metric__meta">
|
|
302
|
+
<span>Last 30 days</span>
|
|
303
|
+
<span class="ct-metric__status">Healthy</span>
|
|
304
|
+
</span>
|
|
305
|
+
</dd>
|
|
306
|
+
</div>
|
|
307
|
+
</dl>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Use `data-state="active"`, `loading`, `error`, or `disabled` for structural state and `data-status` for `success`, `info`, `warning`, `danger`, or `error` semantics. Pair `data-state="loading"` with `aria-busy="true"`; pair disabled content with `aria-disabled="true"`.
|
|
311
|
+
|
|
312
|
+
### Segmented Meter
|
|
313
|
+
|
|
314
|
+
The segmented meter represents a part-to-whole distribution. Its visible legend is required: colour is decorative reinforcement, never the only way a segment is identified. Use the scalar progress bar instead when one `aria-valuenow` value describes the whole component.
|
|
315
|
+
|
|
316
|
+
```html
|
|
317
|
+
<div class="ct-meter" role="group"
|
|
318
|
+
aria-labelledby="delivery-label" aria-describedby="delivery-legend">
|
|
319
|
+
<div class="ct-meter__header">
|
|
320
|
+
<span class="ct-meter__label" id="delivery-label">Delivery status</span>
|
|
321
|
+
<span class="ct-meter__summary">100 items</span>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="ct-meter__track" aria-hidden="true">
|
|
324
|
+
<span class="ct-meter__segment" data-variant="success"
|
|
325
|
+
style="--ct-meter-segment-size: 62%;"></span>
|
|
326
|
+
<span class="ct-meter__segment" data-variant="warning"
|
|
327
|
+
style="--ct-meter-segment-size: 23%;"></span>
|
|
328
|
+
<span class="ct-meter__segment" data-variant="danger"
|
|
329
|
+
style="--ct-meter-segment-size: 15%;"></span>
|
|
330
|
+
</div>
|
|
331
|
+
<ul class="ct-meter__legend" id="delivery-legend">
|
|
332
|
+
<li class="ct-meter__legend-item">
|
|
333
|
+
<span class="ct-meter__marker" data-variant="success" aria-hidden="true"></span>
|
|
334
|
+
<span>Completed</span><span class="ct-meter__legend-value">62%</span>
|
|
335
|
+
</li>
|
|
336
|
+
<li class="ct-meter__legend-item">
|
|
337
|
+
<span class="ct-meter__marker" data-variant="warning" aria-hidden="true"></span>
|
|
338
|
+
<span>In review</span><span class="ct-meter__legend-value">23%</span>
|
|
339
|
+
</li>
|
|
340
|
+
<li class="ct-meter__legend-item">
|
|
341
|
+
<span class="ct-meter__marker" data-variant="danger" aria-hidden="true"></span>
|
|
342
|
+
<span>Blocked</span><span class="ct-meter__legend-value">15%</span>
|
|
343
|
+
</li>
|
|
344
|
+
</ul>
|
|
345
|
+
</div>
|
|
346
|
+
```
|
|
347
|
+
|
|
263
348
|
### Modal
|
|
264
349
|
|
|
265
350
|
Dialog with backdrop:
|
|
266
351
|
|
|
267
352
|
```html
|
|
268
|
-
<div class="ct-modal" data-state="
|
|
269
|
-
<div class="ct-modal__dialog">
|
|
353
|
+
<div class="ct-modal" data-state="closed" hidden>
|
|
354
|
+
<div class="ct-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
|
270
355
|
<div class="ct-modal__header">
|
|
271
356
|
<h2 id="modal-title">Invite team</h2>
|
|
272
|
-
<button class="ct-button ct-button--ghost"
|
|
357
|
+
<button class="ct-button ct-button--ghost" type="button"
|
|
358
|
+
aria-label="Close" data-ct-dismiss>×</button>
|
|
273
359
|
</div>
|
|
274
360
|
<div class="ct-modal__body">
|
|
275
361
|
<p>Send an invite to a new team member.</p>
|
|
276
362
|
<!-- form content -->
|
|
277
363
|
</div>
|
|
278
364
|
<div class="ct-modal__footer">
|
|
279
|
-
<button class="ct-button ct-button--secondary">Cancel</button>
|
|
280
|
-
<button class="ct-button">Send</button>
|
|
365
|
+
<button class="ct-button ct-button--secondary" type="button" data-ct-dismiss>Cancel</button>
|
|
366
|
+
<button class="ct-button" type="button">Send</button>
|
|
281
367
|
</div>
|
|
282
368
|
</div>
|
|
283
369
|
</div>
|
|
284
370
|
```
|
|
285
371
|
|
|
372
|
+
Attach `createModalController({ container, dialog, trigger })` to synchronize state, trap focus,
|
|
373
|
+
handle Escape/backdrop dismissal, and restore focus. See [Headless behaviors](../docs/behaviors.md).
|
|
374
|
+
|
|
286
375
|
### Toast
|
|
287
376
|
|
|
288
377
|
Notification with variants:
|
|
@@ -292,12 +381,12 @@ Notification with variants:
|
|
|
292
381
|
<div class="ct-toast" data-variant="success" data-state="open">
|
|
293
382
|
<div class="ct-toast__title">Saved</div>
|
|
294
383
|
<div class="ct-toast__description">Your changes were saved.</div>
|
|
295
|
-
<button class="ct-button ct-button--ghost">Undo</button>
|
|
384
|
+
<button class="ct-button ct-button--ghost" type="button">Undo</button>
|
|
296
385
|
</div>
|
|
297
386
|
</div>
|
|
298
387
|
```
|
|
299
388
|
|
|
300
|
-
Variants: `success`, `
|
|
389
|
+
Variants: `success`, `danger`, `info`, `warning`
|
|
301
390
|
|
|
302
391
|
### Alert / Banner
|
|
303
392
|
|
|
@@ -310,7 +399,7 @@ Inline feedback message with variants:
|
|
|
310
399
|
<div class="ct-alert__title">Action required</div>
|
|
311
400
|
<div class="ct-alert__description">Please review the pending changes.</div>
|
|
312
401
|
<div class="ct-alert__actions">
|
|
313
|
-
<button class="ct-button ct-button--secondary ct-button--sm">Review</button>
|
|
402
|
+
<button class="ct-button ct-button--secondary ct-button--sm" type="button">Review</button>
|
|
314
403
|
</div>
|
|
315
404
|
</div>
|
|
316
405
|
</div>
|
|
@@ -330,6 +419,21 @@ Small status indicators with optional icon or dot:
|
|
|
330
419
|
</span>
|
|
331
420
|
```
|
|
332
421
|
|
|
422
|
+
### Live / Connection Status
|
|
423
|
+
|
|
424
|
+
Status text is mandatory; the indicator is decorative. Static statuses need no live-region role. For a connection label that changes in place, use a polite atomic status region.
|
|
425
|
+
|
|
426
|
+
```html
|
|
427
|
+
<span class="ct-status" data-status="connecting" data-state="loading"
|
|
428
|
+
role="status" aria-live="polite" aria-atomic="true" aria-busy="true">
|
|
429
|
+
<span class="ct-status__indicator" aria-hidden="true"></span>
|
|
430
|
+
<span class="ct-status__label">Connecting</span>
|
|
431
|
+
<span class="ct-status__detail">Retrying</span>
|
|
432
|
+
</span>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Connection values are `online`, `connecting`, `degraded`, `offline`, and `error`. Generic semantic aliases `success`, `info`, `warning`, `neutral`, and `danger` are also supported. Update the existing label in place so screen readers announce the change once.
|
|
436
|
+
|
|
333
437
|
### Chip / Tag
|
|
334
438
|
|
|
335
439
|
Compact tags for filters or metadata:
|
|
@@ -348,7 +452,9 @@ Compact tags for filters or metadata:
|
|
|
348
452
|
|
|
349
453
|
### File Upload / Drag & Drop
|
|
350
454
|
|
|
351
|
-
Drag area + file list pattern. Use `data-state="dragover"`
|
|
455
|
+
Drag area + file list pattern. Use `data-state="dragover"` while dragging. For validation, put
|
|
456
|
+
`aria-invalid="true"` on both the styled dropzone and the native file input, then connect visible
|
|
457
|
+
error text to the input with `aria-describedby`.
|
|
352
458
|
|
|
353
459
|
```html
|
|
354
460
|
<div class="ct-file-upload">
|
|
@@ -368,7 +474,18 @@ Drag area + file list pattern. Use `data-state="dragover"` and `aria-invalid="tr
|
|
|
368
474
|
<span class="ct-badge ct-badge--success">Uploaded</span>
|
|
369
475
|
</li>
|
|
370
476
|
</ul>
|
|
371
|
-
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<!-- Invalid state -->
|
|
480
|
+
<div class="ct-file-upload">
|
|
481
|
+
<label class="ct-file-upload__dropzone" for="files-invalid" aria-invalid="true">
|
|
482
|
+
<input class="ct-file-upload__input" id="files-invalid" type="file"
|
|
483
|
+
aria-invalid="true" aria-describedby="files-error" />
|
|
484
|
+
<div class="ct-file-upload__title">Choose another file</div>
|
|
485
|
+
<div class="ct-file-upload__hint">PDF, DOCX up to 10MB</div>
|
|
486
|
+
<span class="ct-button ct-button--secondary ct-button--sm">Browse files</span>
|
|
487
|
+
</label>
|
|
488
|
+
<div class="ct-file-upload__error" id="files-error">File is too large.</div>
|
|
372
489
|
</div>
|
|
373
490
|
```
|
|
374
491
|
|
|
@@ -377,7 +494,9 @@ Drag area + file list pattern. Use `data-state="dragover"` and `aria-invalid="tr
|
|
|
377
494
|
Spinner for inline loading and overlay for blocking states:
|
|
378
495
|
|
|
379
496
|
```html
|
|
380
|
-
<div class="ct-spinner ct-spinner--sm" role="status" aria-live="polite"
|
|
497
|
+
<div class="ct-spinner ct-spinner--sm" role="status" aria-live="polite">
|
|
498
|
+
<span class="ct-sr-only">Loading…</span>
|
|
499
|
+
</div>
|
|
381
500
|
|
|
382
501
|
<div class="ct-card" style="position: relative; min-height: 180px;">
|
|
383
502
|
<div class="ct-loading-overlay" data-state="active" aria-busy="true">
|
|
@@ -394,8 +513,9 @@ Spinner for inline loading and overlay for blocking states:
|
|
|
394
513
|
Use the modal base with a confirmation layout:
|
|
395
514
|
|
|
396
515
|
```html
|
|
397
|
-
<div class="ct-modal ct-modal--confirmation" data-state="open"
|
|
398
|
-
<div class="ct-modal__dialog"
|
|
516
|
+
<div class="ct-modal ct-modal--confirmation" data-state="open">
|
|
517
|
+
<div class="ct-modal__dialog" role="dialog" aria-modal="true"
|
|
518
|
+
aria-labelledby="confirm-title" aria-describedby="confirm-desc">
|
|
399
519
|
<div class="ct-modal__body">
|
|
400
520
|
<div class="ct-confirmation" data-variant="danger">
|
|
401
521
|
<div class="ct-confirmation__icon" aria-hidden="true">!</div>
|
|
@@ -406,8 +526,8 @@ Use the modal base with a confirmation layout:
|
|
|
406
526
|
</div>
|
|
407
527
|
</div>
|
|
408
528
|
<div class="ct-modal__footer">
|
|
409
|
-
<button class="ct-button ct-button--secondary">Cancel</button>
|
|
410
|
-
<button class="ct-button ct-button--danger">Delete</button>
|
|
529
|
+
<button class="ct-button ct-button--secondary" type="button" data-ct-dismiss>Cancel</button>
|
|
530
|
+
<button class="ct-button ct-button--danger" type="button">Delete</button>
|
|
411
531
|
</div>
|
|
412
532
|
</div>
|
|
413
533
|
</div>
|
|
@@ -432,31 +552,41 @@ Tab navigation with panels:
|
|
|
432
552
|
```html
|
|
433
553
|
<div class="ct-tabs">
|
|
434
554
|
<div class="ct-tabs__list" role="tablist">
|
|
435
|
-
<button class="ct-tabs__trigger" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">Overview</button>
|
|
436
|
-
<button class="ct-tabs__trigger" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">Settings</button>
|
|
555
|
+
<button class="ct-tabs__trigger" type="button" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">Overview</button>
|
|
556
|
+
<button class="ct-tabs__trigger" type="button" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">Settings</button>
|
|
437
557
|
</div>
|
|
438
558
|
<div class="ct-tabs__panel" role="tabpanel" id="panel-1" aria-labelledby="tab-1">
|
|
439
559
|
<p>Panel content</p>
|
|
440
560
|
</div>
|
|
561
|
+
<div class="ct-tabs__panel" role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
|
|
562
|
+
<p>Settings content</p>
|
|
563
|
+
</div>
|
|
441
564
|
</div>
|
|
442
565
|
```
|
|
443
566
|
|
|
567
|
+
Attach `createTabsController({ tablist })` unless the consuming framework already owns the equivalent roving-focus and selection state machine.
|
|
568
|
+
|
|
444
569
|
### Dropdown
|
|
445
570
|
|
|
446
571
|
Action menu:
|
|
447
572
|
|
|
448
573
|
```html
|
|
449
|
-
<div class="ct-dropdown" data-state="
|
|
450
|
-
<button class="ct-button ct-button--secondary ct-dropdown__trigger"
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
574
|
+
<div class="ct-dropdown" data-state="closed">
|
|
575
|
+
<button class="ct-button ct-button--secondary ct-dropdown__trigger" type="button"
|
|
576
|
+
id="actions-trigger" aria-expanded="false" aria-haspopup="menu"
|
|
577
|
+
aria-controls="actions-menu">Actions</button>
|
|
578
|
+
<div class="ct-dropdown__menu" id="actions-menu" role="menu"
|
|
579
|
+
aria-labelledby="actions-trigger" hidden>
|
|
580
|
+
<button class="ct-dropdown__item" type="button" role="menuitem" tabindex="-1">Edit</button>
|
|
581
|
+
<button class="ct-dropdown__item" type="button" role="menuitem" tabindex="-1">Duplicate</button>
|
|
454
582
|
<div class="ct-dropdown__separator" role="separator"></div>
|
|
455
|
-
<button class="ct-dropdown__item" type="button">Delete</button>
|
|
583
|
+
<button class="ct-dropdown__item" type="button" role="menuitem" tabindex="-1">Delete</button>
|
|
456
584
|
</div>
|
|
457
585
|
</div>
|
|
458
586
|
```
|
|
459
587
|
|
|
588
|
+
Attach `createDropdownController({ root, trigger, menu })` to own `hidden`, `data-state`, ARIA, keyboard navigation, selection, outside interaction, and focus return. The caller still gives the menu an accessible name with `aria-label` or `aria-labelledby`.
|
|
589
|
+
|
|
460
590
|
### Pagination
|
|
461
591
|
|
|
462
592
|
Page navigation:
|
|
@@ -480,14 +610,14 @@ Path navigation:
|
|
|
480
610
|
<ol class="ct-breadcrumbs__list">
|
|
481
611
|
<li class="ct-breadcrumbs__item">
|
|
482
612
|
<a class="ct-breadcrumbs__link" href="/">Home</a>
|
|
483
|
-
<span class="ct-breadcrumbs__separator">/</span>
|
|
613
|
+
<span class="ct-breadcrumbs__separator" aria-hidden="true">/</span>
|
|
484
614
|
</li>
|
|
485
615
|
<li class="ct-breadcrumbs__item">
|
|
486
616
|
<a class="ct-breadcrumbs__link" href="/projects">Projects</a>
|
|
487
|
-
<span class="ct-breadcrumbs__separator">/</span>
|
|
617
|
+
<span class="ct-breadcrumbs__separator" aria-hidden="true">/</span>
|
|
488
618
|
</li>
|
|
489
619
|
<li class="ct-breadcrumbs__item">
|
|
490
|
-
<span class="ct-breadcrumbs__current">Alpha</span>
|
|
620
|
+
<span class="ct-breadcrumbs__current" aria-current="page">Alpha</span>
|
|
491
621
|
</li>
|
|
492
622
|
</ol>
|
|
493
623
|
</nav>
|
|
@@ -495,25 +625,46 @@ Path navigation:
|
|
|
495
625
|
|
|
496
626
|
### Datepicker
|
|
497
627
|
|
|
498
|
-
Calendar popup for date selection
|
|
628
|
+
Calendar popup for date selection. The application or framework adapter owns the calendar's
|
|
629
|
+
roving-tabindex keyboard behavior, month changes, selection, and open/close state described in the
|
|
630
|
+
accessibility guidelines.
|
|
499
631
|
|
|
500
632
|
```html
|
|
501
633
|
<div class="ct-field">
|
|
502
634
|
<label class="ct-field__label" for="date">Date</label>
|
|
503
635
|
<div class="ct-datepicker" data-state="open">
|
|
504
|
-
<input class="ct-input" id="date" type="text" placeholder="Select date"
|
|
505
|
-
|
|
636
|
+
<input class="ct-input" id="date" type="text" placeholder="Select date"
|
|
637
|
+
role="combobox" aria-haspopup="dialog" aria-expanded="true"
|
|
638
|
+
aria-controls="date-popover" />
|
|
639
|
+
<div class="ct-datepicker__popover" id="date-popover"
|
|
640
|
+
role="dialog" aria-modal="true" aria-label="Choose date">
|
|
506
641
|
<div class="ct-datepicker__header">
|
|
507
|
-
<button class="ct-button ct-button--ghost ct-button--icon"
|
|
508
|
-
|
|
509
|
-
<
|
|
642
|
+
<button class="ct-button ct-button--ghost ct-button--icon" type="button"
|
|
643
|
+
aria-label="Previous month">‹</button>
|
|
644
|
+
<div class="ct-datepicker__title" aria-live="polite">March 2026</div>
|
|
645
|
+
<button class="ct-button ct-button--ghost ct-button--icon" type="button"
|
|
646
|
+
aria-label="Next month">›</button>
|
|
510
647
|
</div>
|
|
511
|
-
<div class="ct-datepicker__grid">
|
|
512
|
-
<div class="ct-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
648
|
+
<div class="ct-datepicker__grid" role="grid" aria-label="March 2026">
|
|
649
|
+
<div class="ct-datepicker__row" role="row">
|
|
650
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Monday">Mo</abbr>
|
|
651
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Tuesday">Tu</abbr>
|
|
652
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Wednesday">We</abbr>
|
|
653
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Thursday">Th</abbr>
|
|
654
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Friday">Fr</abbr>
|
|
655
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Saturday">Sa</abbr>
|
|
656
|
+
<abbr class="ct-datepicker__weekday" role="columnheader" title="Sunday">Su</abbr>
|
|
657
|
+
</div>
|
|
658
|
+
<div class="ct-datepicker__row" role="row">
|
|
659
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="-1" aria-label="9 March 2026">9</button>
|
|
660
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="-1" aria-label="10 March 2026">10</button>
|
|
661
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="-1" aria-label="11 March 2026">11</button>
|
|
662
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="0" aria-selected="true" aria-label="12 March 2026">12</button>
|
|
663
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="-1" aria-label="13 March 2026">13</button>
|
|
664
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="-1" aria-label="14 March 2026">14</button>
|
|
665
|
+
<button class="ct-datepicker__day" type="button" role="gridcell" tabindex="-1" aria-label="15 March 2026">15</button>
|
|
666
|
+
</div>
|
|
667
|
+
<!-- Render the remaining weeks with the same row/gridcell contract. -->
|
|
517
668
|
</div>
|
|
518
669
|
</div>
|
|
519
670
|
</div>
|
|
@@ -525,12 +676,57 @@ Calendar popup for date selection:
|
|
|
525
676
|
Hint on hover/focus:
|
|
526
677
|
|
|
527
678
|
```html
|
|
528
|
-
<span class="ct-tooltip" data-state="
|
|
529
|
-
<button class="ct-button" aria-describedby="tip-1">Hover me</button>
|
|
530
|
-
<span class="ct-tooltip__content" role="tooltip" id="tip-1">Short hint</span>
|
|
679
|
+
<span class="ct-tooltip" data-state="closed" data-side="top">
|
|
680
|
+
<button class="ct-button" type="button" aria-describedby="tip-1">Hover me</button>
|
|
681
|
+
<span class="ct-tooltip__content" role="tooltip" id="tip-1" hidden>Short hint</span>
|
|
531
682
|
</span>
|
|
532
683
|
```
|
|
533
684
|
|
|
685
|
+
Attach `createTooltipController({ root, trigger, tooltip })` for hover-capable pointers, keyboard focus, touch dismissal, Escape, delays, and cleanup. The tooltip supplements the trigger's accessible name; it must not contain interactive content.
|
|
686
|
+
|
|
687
|
+
### Navbar
|
|
688
|
+
|
|
689
|
+
Use `ct-navbar--compact` for dense product headers. Mark expendable actions with `data-priority="secondary"`; they hide below the 600px small breakpoint while the menu toggle and primary action remain available. If no mobile toggle exists, primary navigation remains reachable as a horizontal scroll row instead of disappearing.
|
|
690
|
+
|
|
691
|
+
```html
|
|
692
|
+
<header class="ct-navbar ct-navbar--compact">
|
|
693
|
+
<a class="ct-navbar__brand" href="/">
|
|
694
|
+
<span class="ct-navbar__title">Workspace</span>
|
|
695
|
+
</a>
|
|
696
|
+
<button class="ct-navbar__toggle" type="button"
|
|
697
|
+
aria-label="Open navigation" aria-expanded="false" aria-controls="mobile-nav">
|
|
698
|
+
<span class="ct-navbar__toggle-icon" aria-hidden="true"><span></span><span></span><span></span></span>
|
|
699
|
+
</button>
|
|
700
|
+
<div class="ct-navbar__spacer"></div>
|
|
701
|
+
<div class="ct-navbar__actions">
|
|
702
|
+
<button class="ct-button ct-button--ghost ct-button--icon"
|
|
703
|
+
type="button" aria-label="Search" data-priority="secondary">…</button>
|
|
704
|
+
<button class="ct-button ct-button--ghost ct-button--icon"
|
|
705
|
+
type="button" aria-label="Profile">…</button>
|
|
706
|
+
</div>
|
|
707
|
+
<nav class="ct-navbar__mobile-menu" id="mobile-nav" data-state="closed"
|
|
708
|
+
aria-label="Mobile navigation">…</nav>
|
|
709
|
+
</header>
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
The CSS controls presentation only. Application code must keep `aria-expanded`, `data-state`, focus movement, Escape handling, and focus return in sync.
|
|
713
|
+
|
|
714
|
+
### App Shell
|
|
715
|
+
|
|
716
|
+
App Shell V2 is the recommended layout for new applications. The original `.ct-app-shell` remains a supported compatibility layout and is not being rewritten, so existing DOM and selectors keep working. Both variants now share these public override hooks:
|
|
717
|
+
|
|
718
|
+
- `--ct-shell-sidebar-width`
|
|
719
|
+
- `--ct-shell-sidebar-rail-width`
|
|
720
|
+
- `--ct-shell-panel-width`
|
|
721
|
+
- `--ct-shell-motion-duration`
|
|
722
|
+
- `--ct-shell-motion-easing`
|
|
723
|
+
|
|
724
|
+
The historical V1 (`--ct-sidebar-*`) and V2 (`--ct-v2-*`) variables remain compatibility aliases. Prefer the shared hooks in new integrations.
|
|
725
|
+
|
|
726
|
+
## Touch Target Policy
|
|
727
|
+
|
|
728
|
+
On coarse pointers Construct exposes at least a 44×44 CSS-pixel interaction area for the covered compact controls: default and small buttons, checkbox and radio inputs, default and small switches, sliders, pagination links, and interactive chips. Automated browser contracts verify those targets. This exceeds the WCAG 2.2 AA size threshold in SC 2.5.8 and meets Construct's enhanced target policy; SC 2.5.5 itself is AAA. Visual sizes remain compact on fine pointers. Products remain responsible for custom controls and applicable spacing exceptions.
|
|
729
|
+
|
|
534
730
|
## Layout Utilities
|
|
535
731
|
|
|
536
732
|
### Stack
|
|
@@ -549,9 +745,9 @@ Vertical spacing between children:
|
|
|
549
745
|
Horizontal spacing with wrapping:
|
|
550
746
|
|
|
551
747
|
```html
|
|
552
|
-
<div class="ct-cluster" style="--ct-cluster-
|
|
553
|
-
<button>Button 1</button>
|
|
554
|
-
<button>Button 2</button>
|
|
748
|
+
<div class="ct-cluster" style="--ct-cluster-gap: var(--space-3);">
|
|
749
|
+
<button type="button">Button 1</button>
|
|
750
|
+
<button type="button">Button 2</button>
|
|
555
751
|
</div>
|
|
556
752
|
```
|
|
557
753
|
|