luxen-ui 0.25.0 → 0.26.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 +1 -0
- package/bin/cli.mjs +16 -1
- package/cdn/custom-elements.json +402 -276
- package/cdn/elements/alert-dialog/alert-dialog.d.ts +3 -2
- package/cdn/elements/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/cdn/elements/alert-dialog/alert-dialog.js.map +1 -1
- package/cdn/elements/avatar/avatar.js +1 -1
- package/cdn/elements/avatar/avatar.js.map +1 -1
- package/cdn/elements/carousel/carousel.js +1 -1
- package/cdn/elements/carousel/carousel.js.map +1 -1
- package/cdn/elements/combobox/combobox.d.ts.map +1 -1
- package/cdn/elements/combobox/combobox.js +2 -1
- package/cdn/elements/combobox/combobox.js.map +1 -1
- package/cdn/elements/dialog/dialog.d.ts +2 -1
- package/cdn/elements/dialog/dialog.d.ts.map +1 -1
- package/cdn/elements/dialog/dialog.js.map +1 -1
- package/cdn/elements/dialog/dialog.styles.js +1 -1
- package/cdn/elements/dialog/dialog.styles.js.map +1 -1
- package/cdn/elements/drawer/drawer.d.ts +7 -7
- package/cdn/elements/drawer/drawer.js +1 -1
- package/cdn/elements/drawer/drawer.js.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/input-otp/input-otp.d.ts +2 -2
- package/cdn/elements/input-otp/input-otp.js.map +1 -1
- package/cdn/elements/prose-editor/prose-editor.js +1 -1
- package/cdn/elements/prose-editor/prose-editor.js.map +1 -1
- package/cdn/elements/rating/rating.js +1 -1
- package/cdn/elements/rating/rating.js.map +1 -1
- package/cdn/elements/select/select.d.ts.map +1 -1
- package/cdn/elements/select/select.js +2 -1
- package/cdn/elements/select/select.js.map +1 -1
- package/cdn/elements/slider/slider.js +1 -1
- package/cdn/elements/slider/slider.js.map +1 -1
- package/cdn/elements/stories-viewer/stories-viewer.js +1 -1
- package/cdn/elements/stories-viewer/stories-viewer.js.map +1 -1
- package/cdn/elements/table/table.meta.d.ts +55 -0
- package/cdn/elements/table/table.meta.d.ts.map +1 -0
- package/cdn/elements/table/table.meta.js +0 -0
- package/cdn/elements/tag/tag.js +1 -1
- package/cdn/elements/tag/tag.js.map +1 -1
- package/cdn/elements/tree/tree.js +1 -1
- package/cdn/elements/tree/tree.js.map +1 -1
- package/cdn/elements/tree-item/tree-item.js +1 -1
- package/cdn/elements/tree-item/tree-item.js.map +1 -1
- package/cdn/shared/controllers/listbox-nav.d.ts +7 -0
- package/cdn/shared/controllers/listbox-nav.d.ts.map +1 -1
- package/cdn/shared/controllers/listbox-nav.js +1 -1
- package/cdn/shared/controllers/listbox-nav.js.map +1 -1
- package/cdn/shared/styles/button-core.styles.js +1 -1
- package/cdn/shared/styles/button-core.styles.js.map +1 -1
- package/cdn/shared/styles/checkbox-appearance.styles.js +1 -1
- package/cdn/shared/styles/checkbox-appearance.styles.js.map +1 -1
- package/cdn/shared/styles/picker-core.styles.js +1 -1
- package/cdn/shared/styles/picker-core.styles.js.map +1 -1
- package/cdn/standalone.css +284 -67
- package/cdn/standalone.js +38 -28
- package/cdn/standalone.js.map +1 -1
- package/cdn/styles/base.css +11 -0
- package/cdn/styles/elements/breadcrumb.css +2 -2
- package/cdn/styles/elements/button.css +2 -2
- package/cdn/styles/elements/checkbox.css +2 -2
- package/cdn/styles/elements/close-button/circle.css +2 -2
- package/cdn/styles/elements/close-button/ring.css +2 -2
- package/cdn/styles/elements/close-button/square.css +2 -2
- package/cdn/styles/elements/disclosure.css +1 -1
- package/cdn/styles/elements/input-otp.css +19 -3
- package/cdn/styles/elements/input-stepper/default.css +1 -1
- package/cdn/styles/elements/input-stepper/pill.css +1 -1
- package/cdn/styles/elements/input-stepper/rounded.css +1 -1
- package/cdn/styles/elements/input.css +11 -11
- package/cdn/styles/elements/prose-editor.css +6 -6
- package/cdn/styles/elements/radio-group.css +2 -2
- package/cdn/styles/elements/radio.css +2 -2
- package/cdn/styles/elements/segmented-control.css +1 -1
- package/cdn/styles/elements/select.css +5 -5
- package/cdn/styles/elements/story.css +2 -2
- package/cdn/styles/elements/switch.css +2 -2
- package/cdn/styles/elements/table.css +186 -0
- package/cdn/styles/elements/tabs/enclosed.css +3 -3
- package/cdn/styles/elements/tabs/line.css +3 -3
- package/cdn/styles/elements/textarea.css +5 -5
- package/cdn/styles/preset.css +19 -6
- package/cdn/styles/tokens/aliases.css +5 -3
- package/cdn/styles/tokens/forms.css +3 -3
- package/cdn/styles/tokens.css +8 -6
- package/dist/css/base.css +11 -0
- package/dist/css/elements/breadcrumb.css +2 -2
- package/dist/css/elements/button.css +2 -2
- package/dist/css/elements/checkbox.css +2 -2
- package/dist/css/elements/close-button/circle.css +2 -2
- package/dist/css/elements/close-button/ring.css +2 -2
- package/dist/css/elements/close-button/square.css +2 -2
- package/dist/css/elements/disclosure.css +1 -1
- package/dist/css/elements/input-otp.css +19 -3
- package/dist/css/elements/input-stepper/default.css +1 -1
- package/dist/css/elements/input-stepper/pill.css +1 -1
- package/dist/css/elements/input-stepper/rounded.css +1 -1
- package/dist/css/elements/input.css +11 -11
- package/dist/css/elements/prose-editor.css +6 -6
- package/dist/css/elements/radio-group.css +2 -2
- package/dist/css/elements/radio.css +2 -2
- package/dist/css/elements/segmented-control.css +1 -1
- package/dist/css/elements/select.css +5 -5
- package/dist/css/elements/story.css +2 -2
- package/dist/css/elements/switch.css +2 -2
- package/dist/css/elements/table.css +186 -0
- package/dist/css/elements/tabs/enclosed.css +3 -3
- package/dist/css/elements/tabs/line.css +3 -3
- package/dist/css/elements/textarea.css +5 -5
- package/dist/css/preset.css +19 -6
- package/dist/css/tokens/aliases.css +5 -3
- package/dist/css/tokens/forms.css +3 -3
- package/dist/css/tokens.css +8 -6
- package/dist/custom-elements.json +402 -276
- package/dist/elements/alert-dialog/alert-dialog.d.ts +3 -2
- package/dist/elements/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/elements/alert-dialog/alert-dialog.js +3 -2
- package/dist/elements/avatar/avatar.css +2 -2
- package/dist/elements/carousel/carousel.css +18 -0
- package/dist/elements/combobox/combobox.css +9 -0
- package/dist/elements/combobox/combobox.d.ts.map +1 -1
- package/dist/elements/combobox/combobox.js +1 -0
- package/dist/elements/dialog/dialog.css +12 -0
- package/dist/elements/dialog/dialog.d.ts +2 -1
- package/dist/elements/dialog/dialog.d.ts.map +1 -1
- package/dist/elements/dialog/dialog.js +2 -1
- package/dist/elements/drawer/drawer.css +11 -1
- package/dist/elements/drawer/drawer.d.ts +7 -7
- package/dist/elements/drawer/drawer.js +7 -7
- package/dist/elements/dropdown-item/dropdown-item.css +1 -1
- package/dist/elements/input-otp/input-otp.d.ts +2 -2
- package/dist/elements/input-otp/input-otp.js +2 -2
- package/dist/elements/prose-editor/prose-editor.css +5 -3
- package/dist/elements/rating/rating.css +9 -10
- package/dist/elements/select/select.css +10 -5
- package/dist/elements/select/select.d.ts.map +1 -1
- package/dist/elements/select/select.js +1 -0
- package/dist/elements/slider/slider.css +5 -1
- package/dist/elements/stories-viewer/stories-viewer.css +10 -2
- package/dist/elements/table/table.meta.d.ts +55 -0
- package/dist/elements/table/table.meta.d.ts.map +1 -0
- package/dist/elements/table/table.meta.js +65 -0
- package/dist/elements/tag/tag.css +3 -3
- package/dist/elements/tree/tree.css +3 -3
- package/dist/elements/tree-item/tree-item.css +5 -3
- package/dist/metadata/alert-dialog.json +90 -85
- package/dist/metadata/alert.json +22 -22
- package/dist/metadata/avatar.json +26 -26
- package/dist/metadata/badge.json +13 -13
- package/dist/metadata/breadcrumb.json +9 -9
- package/dist/metadata/button.json +40 -40
- package/dist/metadata/carousel.json +133 -133
- package/dist/metadata/checkbox.json +13 -13
- package/dist/metadata/close-button.json +15 -15
- package/dist/metadata/color-scheme-icon.json +13 -13
- package/dist/metadata/combobox.json +77 -94
- package/dist/metadata/dialog.json +49 -44
- package/dist/metadata/disclosure.json +17 -17
- package/dist/metadata/divider.json +12 -12
- package/dist/metadata/drawer.json +61 -61
- package/dist/metadata/dropdown-item.json +50 -50
- package/dist/metadata/dropdown.json +32 -32
- package/dist/metadata/form-field.json +15 -15
- package/dist/metadata/icon.json +8 -8
- package/dist/metadata/index.json +1761 -1765
- package/dist/metadata/input-group.json +5 -5
- package/dist/metadata/input-otp.json +17 -17
- package/dist/metadata/input-stepper.json +24 -24
- package/dist/metadata/input.json +31 -31
- package/dist/metadata/popover.json +49 -49
- package/dist/metadata/progress.json +15 -15
- package/dist/metadata/prose-editor.json +110 -116
- package/dist/metadata/radio.json +15 -15
- package/dist/metadata/rating.json +25 -47
- package/dist/metadata/segmented-control.json +24 -46
- package/dist/metadata/select.json +81 -98
- package/dist/metadata/skeleton.json +12 -12
- package/dist/metadata/slider.json +60 -78
- package/dist/metadata/spinner.json +5 -5
- package/dist/metadata/sticky-bar.json +13 -13
- package/dist/metadata/stories-viewer.json +105 -105
- package/dist/metadata/stories.json +17 -17
- package/dist/metadata/story.json +41 -41
- package/dist/metadata/switch.json +9 -9
- package/dist/metadata/table.json +88 -0
- package/dist/metadata/tabs.json +26 -26
- package/dist/metadata/tag.json +50 -50
- package/dist/metadata/textarea.json +28 -28
- package/dist/metadata/toast.json +16 -16
- package/dist/metadata/tooltip.json +49 -49
- package/dist/metadata/tree-item.json +73 -73
- package/dist/metadata/tree.json +39 -39
- package/dist/shared/controllers/listbox-nav.d.ts +7 -0
- package/dist/shared/controllers/listbox-nav.d.ts.map +1 -1
- package/dist/shared/controllers/listbox-nav.js +12 -0
- package/dist/shared/styles/button-core.css +2 -2
- package/dist/shared/styles/checkbox-appearance.css +2 -2
- package/dist/shared/styles/picker-core.css +13 -10
- package/dist/templates/elements/alert-dialog.md +19 -18
- package/dist/templates/elements/alert.md +4 -4
- package/dist/templates/elements/avatar.md +9 -9
- package/dist/templates/elements/badge.md +4 -4
- package/dist/templates/elements/breadcrumb.md +4 -4
- package/dist/templates/elements/button.md +8 -8
- package/dist/templates/elements/carousel.md +32 -32
- package/dist/templates/elements/checkbox.md +4 -4
- package/dist/templates/elements/close-button.md +2 -2
- package/dist/templates/elements/color-scheme-icon.md +13 -13
- package/dist/templates/elements/combobox.md +19 -19
- package/dist/templates/elements/dialog.md +11 -10
- package/dist/templates/elements/disclosure.md +3 -3
- package/dist/templates/elements/divider.md +2 -2
- package/dist/templates/elements/drawer.md +15 -15
- package/dist/templates/elements/dropdown.md +15 -15
- package/dist/templates/elements/form-field.md +3 -3
- package/dist/templates/elements/icon.md +1 -1
- package/dist/templates/elements/input-otp.md +5 -5
- package/dist/templates/elements/input-stepper.md +4 -4
- package/dist/templates/elements/input.md +15 -8
- package/dist/templates/elements/popover.md +8 -8
- package/dist/templates/elements/progress.md +3 -3
- package/dist/templates/elements/prose-editor.md +38 -39
- package/dist/templates/elements/radio.md +6 -6
- package/dist/templates/elements/rating.md +5 -13
- package/dist/templates/elements/segmented-control.md +4 -4
- package/dist/templates/elements/select.md +23 -23
- package/dist/templates/elements/skeleton.md +2 -2
- package/dist/templates/elements/slider.md +13 -13
- package/dist/templates/elements/spinner.md +1 -1
- package/dist/templates/elements/sticky-bar.md +2 -2
- package/dist/templates/elements/stories-viewer.md +29 -29
- package/dist/templates/elements/stories.md +4 -4
- package/dist/templates/elements/story.md +11 -11
- package/dist/templates/elements/switch.md +5 -5
- package/dist/templates/elements/table.md +210 -0
- package/dist/templates/elements/tabs.md +4 -4
- package/dist/templates/elements/tag.md +14 -14
- package/dist/templates/elements/textarea.md +8 -8
- package/dist/templates/elements/toast.md +4 -4
- package/dist/templates/elements/tooltip.md +9 -9
- package/dist/templates/elements/tree-item.md +18 -18
- package/dist/templates/elements/tree.md +13 -13
- package/dist/templates/foundations/focus.md +303 -0
- package/elements.json +9 -0
- package/package.json +1 -1
- package/postcss-plugin-prefix.js +2 -2
- package/templates/SKILL.md.tpl +1 -0
- package/templates/choosing-elements.md.tpl +1 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
---
|
|
2
|
+
outline: deep
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Focus
|
|
6
|
+
|
|
7
|
+
The focus indicator shows where the keyboard is. Every Luxen element draws it from the same three tokens, on `:focus-visible`, as an `outline`. Press <kbd>Tab</kbd>:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<div class="flex flex-wrap items-center gap-4">
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
class="l-button"
|
|
14
|
+
data-variant="primary"
|
|
15
|
+
>
|
|
16
|
+
Save
|
|
17
|
+
</button>
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
class="l-button"
|
|
21
|
+
>
|
|
22
|
+
Cancel
|
|
23
|
+
</button>
|
|
24
|
+
<label class="flex items-center gap-2 text-sm">
|
|
25
|
+
<input
|
|
26
|
+
type="checkbox"
|
|
27
|
+
class="l-checkbox"
|
|
28
|
+
/>
|
|
29
|
+
Remember me
|
|
30
|
+
</label>
|
|
31
|
+
<label class="flex items-center gap-2 text-sm">
|
|
32
|
+
<input
|
|
33
|
+
type="checkbox"
|
|
34
|
+
role="switch"
|
|
35
|
+
class="l-switch"
|
|
36
|
+
/>
|
|
37
|
+
Notifications
|
|
38
|
+
</label>
|
|
39
|
+
<l-segmented-control
|
|
40
|
+
label="View"
|
|
41
|
+
value="list"
|
|
42
|
+
>
|
|
43
|
+
<button value="list">List</button>
|
|
44
|
+
<button value="board">Board</button>
|
|
45
|
+
</l-segmented-control>
|
|
46
|
+
<input
|
|
47
|
+
class="l-input w-40"
|
|
48
|
+
placeholder="Search…"
|
|
49
|
+
aria-label="Search"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Anatomy
|
|
55
|
+
|
|
56
|
+
| Part | Token | Default |
|
|
57
|
+
| ------ | ---------------------------------------------- | ----------------------------------- |
|
|
58
|
+
| Offset | `--l-focus-ring-offset` | `2px` |
|
|
59
|
+
| Ring | `--l-focus-ring-width`, `--l-focus-ring-color` | `2px`, `#d9461f` |
|
|
60
|
+
| Halo | `--l-form-control-focus-halo-width` | `3px`, the ring color at 22% |
|
|
61
|
+
| Border | `--l-form-control-border-width` | `1px`, in the ring color |
|
|
62
|
+
| Stroke | `--l-form-control-focus-border-width` | `2px`, drawn inward over the border |
|
|
63
|
+
|
|
64
|
+
An invalid field takes `--l-form-control-border-color-invalid` for its halo, border and stroke.
|
|
65
|
+
|
|
66
|
+
## Placements
|
|
67
|
+
|
|
68
|
+
| Placement | Drawn | Used by |
|
|
69
|
+
| ------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| Outside | `outline-offset: var(--l-focus-ring-offset)` | Button, link, checkbox, radio, switch, tag, avatar, breadcrumb, slider thumb, rating star, carousel buttons |
|
|
71
|
+
| Inside | `outline-offset: calc(var(--l-focus-ring-width) * -1)` | Menu item, tabs, segmented control, disclosure summary, input stepper, carousel dots — items set edge to edge, where an outside ring would overlap the neighbor |
|
|
72
|
+
| Field | Border recolored, stroke inward, halo outside (`box-shadow`) | Input, textarea, select, `l-input-group`, `l-select` and its search box, `l-combobox`, `l-prose-editor`, the active `l-input-otp` cell |
|
|
73
|
+
| Active option | Inside ring, once the arrow keys moved it | `l-select` and `l-combobox` options |
|
|
74
|
+
|
|
75
|
+
In a listbox, DOM focus stays on the field and `aria-activedescendant` points at the option. The browser draws no focus there, so the option draws the ring itself when the keyboard moved it. A mouse-driven highlight stays a tint, like a hover.
|
|
76
|
+
|
|
77
|
+
## Shown when it helps
|
|
78
|
+
|
|
79
|
+
[`:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) lets the browser decide whether focus needs an indicator. Luxen never styles `:focus` alone.
|
|
80
|
+
|
|
81
|
+
| Focus arrives by | On | Indicator |
|
|
82
|
+
| -------------------------- | -------------------------- | ------------------------------ |
|
|
83
|
+
| <kbd>Tab</kbd>, arrow keys | Any control | Shown |
|
|
84
|
+
| Click or tap | Button, checkbox, tab, row | Hidden |
|
|
85
|
+
| Click or tap | Text field | Shown — typing follows |
|
|
86
|
+
| Script (`element.focus()`) | Any control | Same as the element focus left |
|
|
87
|
+
|
|
88
|
+
When a script moves focus after a click and the user must see where it landed, force it: `element.focus({ focusVisible: true })`.
|
|
89
|
+
|
|
90
|
+
## Contrast
|
|
91
|
+
|
|
92
|
+
The ring needs **3:1** against the surface it sits on. The default passes on Luxen surfaces in both schemes, not on every background you may put under it.
|
|
93
|
+
|
|
94
|
+
Tokens cascade: set the color on the container that changes the background.
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
.hero {
|
|
98
|
+
--l-focus-ring-color: white;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<div
|
|
104
|
+
class="flex w-full flex-wrap items-center gap-4 rounded-lg bg-[#1e3a8a] p-6 [color-scheme:dark] [--l-focus-ring-color:white]"
|
|
105
|
+
>
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
class="l-button"
|
|
109
|
+
data-variant="primary"
|
|
110
|
+
>
|
|
111
|
+
Get started
|
|
112
|
+
</button>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
class="l-button"
|
|
116
|
+
>
|
|
117
|
+
Learn more
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Over a photo or a video no single color is safe. Use two — a light ring edged with dark ([WCAG technique C40](https://www.w3.org/WAI/WCAG22/Techniques/css/C40)), as `l-stories-viewer` does for the buttons over its media:
|
|
123
|
+
|
|
124
|
+
```css
|
|
125
|
+
.on-media :focus-visible {
|
|
126
|
+
outline: var(--l-focus-ring-width) solid white;
|
|
127
|
+
outline-offset: var(--l-focus-ring-offset);
|
|
128
|
+
box-shadow: 0 0 0 calc(var(--l-focus-ring-offset) + var(--l-focus-ring-width) * 2) black;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Never hidden
|
|
133
|
+
|
|
134
|
+
A sticky header or bar can cover the element that just received focus. Reserve their height on the scroll container:
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
html {
|
|
138
|
+
scroll-padding-block: 4rem 5rem; /* sticky header, sticky bar */
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A container with `overflow: hidden` clips an outside ring. Give it padding of at least offset + width (`4px` by default).
|
|
143
|
+
|
|
144
|
+
## Forced colors
|
|
145
|
+
|
|
146
|
+
In forced colors mode (Windows High Contrast), the browser drops every `box-shadow` and repaints `outline` in a system color. Luxen rings are outlines. A field's halo is a shadow, so fields add a transparent outline there for the system to repaint.
|
|
147
|
+
|
|
148
|
+
## Variants
|
|
149
|
+
|
|
150
|
+
Deliberate departures from the three tokens, each for a reason:
|
|
151
|
+
|
|
152
|
+
| Where | Variant | Why |
|
|
153
|
+
| ------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
|
|
154
|
+
| Invalid field | Border, stroke and halo in `--l-form-control-border-color-invalid` | The error stays readable while the field is focused |
|
|
155
|
+
| Destructive menu item | Ring in `--l-color-text-danger` | Matches the item's own danger color |
|
|
156
|
+
| Tree row, prose editor toolbar button | Half the offset | Rows and buttons sit edge to edge; a full gap reaches the neighbor |
|
|
157
|
+
| Carousel buttons | Gap filled with `--l-color-surface` | They float over slides: the ring always sits next to a known color |
|
|
158
|
+
| Stories viewer buttons | Light ring edged with a dark band | They float over video, whose brightness is unknown |
|
|
159
|
+
| `l-input-otp`, `l-prose-editor` | `--cell-focus-color`, `--border-color-focus` | Per-element color override, defaulting to `--l-focus-ring-color` |
|
|
160
|
+
|
|
161
|
+
## Your own controls
|
|
162
|
+
|
|
163
|
+
Plain links, `<button>` and `<summary>` already take the ring from Luxen's base CSS (zero specificity: any rule of yours wins). For anything else, reuse the tokens so custom controls match:
|
|
164
|
+
|
|
165
|
+
```css
|
|
166
|
+
.card-link:focus-visible {
|
|
167
|
+
outline: var(--l-focus-ring-width) solid var(--l-focus-ring-color);
|
|
168
|
+
outline-offset: var(--l-focus-ring-offset);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Inside a clipping container: flush with the inner edge */
|
|
172
|
+
.menu-row:focus-visible {
|
|
173
|
+
outline: var(--l-focus-ring-width) solid var(--l-focus-ring-color);
|
|
174
|
+
outline-offset: calc(var(--l-focus-ring-width) * -1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Ring on a wrapper whose focusable child is visually hidden */
|
|
178
|
+
.swatch:has(input:focus-visible) {
|
|
179
|
+
outline: var(--l-focus-ring-width) solid var(--l-focus-ring-color);
|
|
180
|
+
outline-offset: var(--l-focus-ring-offset);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Theming
|
|
185
|
+
|
|
186
|
+
### Brand color
|
|
187
|
+
|
|
188
|
+
One value per scheme with `light-dark()` — 6.9:1 on white, 8.8:1 on the dark surface. For a neutral ring that follows the scheme and forced colors on its own, use the system color `CanvasText`.
|
|
189
|
+
|
|
190
|
+
```css
|
|
191
|
+
:root {
|
|
192
|
+
--l-focus-ring-color: light-dark(oklch(48% 0.2 265), oklch(78% 0.14 265));
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### One thickness everywhere
|
|
197
|
+
|
|
198
|
+
Point the field stroke at the ring width and drop the halo:
|
|
199
|
+
|
|
200
|
+
```css
|
|
201
|
+
:root {
|
|
202
|
+
--l-focus-ring-width: 3px;
|
|
203
|
+
--l-form-control-focus-border-width: var(--l-focus-ring-width);
|
|
204
|
+
--l-form-control-focus-halo-width: 0;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
<div
|
|
210
|
+
class="flex flex-wrap items-center gap-4 [--l-focus-ring-width:3px] [--l-form-control-focus-border-width:var(--l-focus-ring-width)] [--l-form-control-focus-halo-width:0px]"
|
|
211
|
+
>
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
class="l-button"
|
|
215
|
+
>
|
|
216
|
+
Save
|
|
217
|
+
</button>
|
|
218
|
+
<label class="flex items-center gap-2 text-sm">
|
|
219
|
+
<input
|
|
220
|
+
type="checkbox"
|
|
221
|
+
class="l-checkbox"
|
|
222
|
+
/>
|
|
223
|
+
Remember me
|
|
224
|
+
</label>
|
|
225
|
+
<input
|
|
226
|
+
class="l-input w-48"
|
|
227
|
+
placeholder="Full name"
|
|
228
|
+
aria-label="Full name"
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Crisp field
|
|
234
|
+
|
|
235
|
+
The stroke alone, no halo. The stroke is drawn inward: the field keeps its size and its text does not move.
|
|
236
|
+
|
|
237
|
+
```css
|
|
238
|
+
:root {
|
|
239
|
+
--l-form-control-focus-halo-width: 0;
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<div class="grid max-w-sm gap-4 [--l-form-control-focus-halo-width:0px]">
|
|
245
|
+
<l-form-field>
|
|
246
|
+
<label>Start date</label>
|
|
247
|
+
<input type="date" />
|
|
248
|
+
</l-form-field>
|
|
249
|
+
<l-form-field>
|
|
250
|
+
<label>Plan</label>
|
|
251
|
+
<select class="l-select">
|
|
252
|
+
<option>Starter</option>
|
|
253
|
+
<option>Pro</option>
|
|
254
|
+
</select>
|
|
255
|
+
</l-form-field>
|
|
256
|
+
<l-select
|
|
257
|
+
label="Team"
|
|
258
|
+
placeholder="Pick a team…"
|
|
259
|
+
>
|
|
260
|
+
<datalist>
|
|
261
|
+
<option value="design">Design</option>
|
|
262
|
+
<option value="dev">Development</option>
|
|
263
|
+
</datalist>
|
|
264
|
+
</l-select>
|
|
265
|
+
<l-combobox
|
|
266
|
+
label="Country"
|
|
267
|
+
placeholder="Search a country…"
|
|
268
|
+
>
|
|
269
|
+
<datalist>
|
|
270
|
+
<option value="fr">France</option>
|
|
271
|
+
<option value="de">Germany</option>
|
|
272
|
+
</datalist>
|
|
273
|
+
</l-combobox>
|
|
274
|
+
</div>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Accessibility
|
|
278
|
+
|
|
279
|
+
- **Visible** — Every focusable element shows an indicator on keyboard focus
|
|
280
|
+
- **Contrast** — The indicator has 3:1 against adjacent colors: 4.3:1 on white, 4.1:1 on the dark surface
|
|
281
|
+
- **Size** — Outside rings are a solid 2px perimeter, the minimum area of Focus Appearance (AAA). Inside rings and the field stroke are drawn inward, which covers less: set `--l-focus-ring-width` and `--l-form-control-focus-border-width` to `3px` to meet it everywhere
|
|
282
|
+
- **Not obscured** — Rings between items set edge to edge are drawn inside, so none is covered by a neighbor; `scroll-padding` keeps focus clear of sticky content
|
|
283
|
+
- **Forced colors** — Indicators are outlines, repainted by the system; never a `box-shadow` alone
|
|
284
|
+
- **Order** — Focus follows DOM order; composite widgets (tabs, tree, menu, radio group) are one Tab stop with arrow keys inside, and focus is never trapped
|
|
285
|
+
|
|
286
|
+
### Rules
|
|
287
|
+
|
|
288
|
+
- Never write `outline: none` without an equally visible replacement
|
|
289
|
+
- Style `:focus-visible`, not `:focus`
|
|
290
|
+
- Draw indicators with `outline`; a `box-shadow` alone disappears in forced colors
|
|
291
|
+
- Keep `--l-focus-ring-width` at `2px` or more
|
|
292
|
+
- Check `--l-focus-ring-color` at 3:1 against every background it lands on, in light and dark; scope it where a background differs
|
|
293
|
+
- Set `scroll-padding` on the scroll container when the page has sticky headers or bars
|
|
294
|
+
- Never use a positive `tabindex`
|
|
295
|
+
|
|
296
|
+
## Tokens
|
|
297
|
+
|
|
298
|
+
- `--l-focus-ring-color` (default: `#d9461f`) — Color of the focus indicator, shown on :focus-visible: the ring around buttons, checkbox, radio, switch, tabs, tags, menu and tree items, links, and the border and halo of a focused text field. Keep at least 3:1 against every surface it lands on (WCAG 1.4.11) — the default gives 4.3:1 on white and 4.1:1 on the dark surface. Re-theme it on an ancestor to scope it, e.g. a lighter color over a photo.
|
|
299
|
+
- `--l-focus-ring-width` (default: `2px`) — Thickness of the :focus-visible ring drawn around interactive elements — buttons, checkbox, radio, switch, slider thumb, rating, tabs, tags, menu and tree items, toolbar and adornment buttons, close buttons, avatars, breadcrumb links. Keep it at 2px or more (WCAG 2.4.13). Rings set inside their element (items laid edge to edge — menu items, tabs, segmented control — where an outer ring would overlap a neighbor or the panel edge) grow inward with it. Text fields show focus on their border instead: see --l-form-control-focus-border-width, which a consumer can point at this token for one focus thickness everywhere.
|
|
300
|
+
- `--l-focus-ring-offset` (default: `2px`) — Gap between an element and its :focus-visible ring, so the ring reads apart from the element's own border or fill. Rings set inside their element (menu items, tabs, segmented control, disclosure summary — items laid edge to edge, where an outer ring would overlap a neighbor or the panel edge) ignore it and sit flush inside the edge instead.
|
|
301
|
+
|
|
302
|
+
- `--l-form-control-focus-border-width` (default: `2px`) — Width of the stroke drawn on a focused text field (input, textarea, native select, input group, l-select and its search box, l-combobox, l-prose-editor, the active l-input-otp cell), in the focus-ring color (the invalid color on an invalid field). Drawn inward from the outer edge, over the border: the control keeps its size and its text does not move. The default `2px` covers the 1px border and 1px more inside, so a focused field reads as clearly as a ring around a button. A value at or below --l-form-control-border-width adds nothing visible: the resting border alone takes the focus color.
|
|
303
|
+
- `--l-form-control-focus-halo-width` (default: `3px`) — Width of the soft halo around a focused text field — a ring of the focus-ring color (the invalid color on an invalid field) at 22% opacity, outside the border. Set to `0` for a crisp stroke with no halo, typically together with --l-form-control-focus-border-width. The color is mixed at the control, so a --l-focus-ring-color re-themed on an ancestor recolors the halo too.
|
package/elements.json
CHANGED
|
@@ -356,6 +356,15 @@
|
|
|
356
356
|
"inMockups": true,
|
|
357
357
|
"selector": ".l-switch"
|
|
358
358
|
},
|
|
359
|
+
{
|
|
360
|
+
"name": "table",
|
|
361
|
+
"displayName": "Table",
|
|
362
|
+
"kind": "native",
|
|
363
|
+
"type": "native",
|
|
364
|
+
"inSkill": true,
|
|
365
|
+
"inMockups": true,
|
|
366
|
+
"selector": ".l-table"
|
|
367
|
+
},
|
|
359
368
|
{
|
|
360
369
|
"name": "tabs",
|
|
361
370
|
"displayName": "Tabs",
|
package/package.json
CHANGED
package/postcss-plugin-prefix.js
CHANGED
|
@@ -70,14 +70,14 @@ plugin.postcss = true;
|
|
|
70
70
|
*
|
|
71
71
|
* Each element ships its shadow-DOM CSS baked into the element JS, built once
|
|
72
72
|
* with the default `l` prefix — that CSS never passes through this plugin, so
|
|
73
|
-
* it keeps reading `var(--l-focus-ring)` etc. Meanwhile the consumer's imported
|
|
73
|
+
* it keeps reading `var(--l-focus-ring-color)` etc. Meanwhile the consumer's imported
|
|
74
74
|
* preset now only defines `--{cssPrefix}-*`. Without a bridge the two
|
|
75
75
|
* namespaces never meet and every shadow token resolves to nothing.
|
|
76
76
|
*
|
|
77
77
|
* So for every `--l-*` token declaration we just renamed, append a one-time
|
|
78
78
|
* `:root` alias pointing the canonical name at the prefixed one:
|
|
79
79
|
*
|
|
80
|
-
* :root { --l-focus-ring: var(--p-focus-ring); … }
|
|
80
|
+
* :root { --l-focus-ring-color: var(--p-focus-ring-color); … }
|
|
81
81
|
*
|
|
82
82
|
* The block rides along with whichever imported file defines the tokens
|
|
83
83
|
* (preset / tokens / aliases), stays exhaustive automatically (it mirrors
|
package/templates/SKILL.md.tpl
CHANGED
|
@@ -42,6 +42,7 @@ These rules apply across all elements; element-specific detail lives in the per-
|
|
|
42
42
|
- **Variants and state use `data-*` attributes** (`data-variant`, `data-appearance`), not modifier classes.
|
|
43
43
|
- **Show/hide overlays with the invoker pattern**: `command="--show"` / `command="--hide"` + `commandfor="<id>"` for dialog and drawer.
|
|
44
44
|
- **Colors, spacing, and typography come from design tokens** — see [references/tokens.md](references/tokens.md). Use the semantic tokens and utility classes listed there rather than arbitrary values.
|
|
45
|
+
- **Focus states follow [references/foundations/focus.md](references/foundations/focus.md)** — read it before styling `:focus-visible` on any control, overriding a focus token, or placing controls on a custom background.
|
|
45
46
|
- **Customize through the documented surface** — CSS custom properties, CSS parts, events. Before overriding an element's styles through `::part()` or working around one of its behaviours (motion, event timing), check its spec: the knob usually exists. When it does not, that is a gap in {{name}} — report it upstream rather than shipping a local workaround.
|
|
46
47
|
|
|
47
48
|
## Choosing an element
|
|
@@ -109,6 +109,7 @@ the platform genuinely can't express the behavior (a modal, a combobox, a toast)
|
|
|
109
109
|
| A keyboard key | `.l-kbd` |
|
|
110
110
|
| A separator between content | `<l-divider>` |
|
|
111
111
|
| Hierarchical, expandable data | `<l-tree>` |
|
|
112
|
+
| Records in rows and columns | `.l-table` (on `<table>`) |
|
|
112
113
|
|
|
113
114
|
### Navigate or organize
|
|
114
115
|
|