@ui-sdk/vue 0.0.0-stage → 0.1.0-beta.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/LICENSE +202 -0
- package/README.md +10 -2
- package/dist/accordion.d.ts +561 -0
- package/dist/accordion.js +24 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +7 -0
- package/dist/alert-dialog.d.ts +231 -0
- package/dist/alert-dialog.js +33 -0
- package/dist/async-options.d.ts +18 -0
- package/dist/async-options.js +40 -0
- package/dist/autocomplete.d.ts +384 -0
- package/dist/autocomplete.js +344 -0
- package/dist/avatar.d.ts +40 -0
- package/dist/avatar.js +28 -0
- package/dist/button.d.ts +52 -0
- package/dist/button.js +43 -0
- package/dist/calendar-context.d.ts +10 -0
- package/dist/calendar-context.js +7 -0
- package/dist/calendar.d.ts +311 -0
- package/dist/calendar.js +349 -0
- package/dist/carousel.d.ts +236 -0
- package/dist/carousel.js +204 -0
- package/dist/checkbox.d.ts +211 -0
- package/dist/checkbox.js +151 -0
- package/dist/children.d.ts +41 -0
- package/dist/children.js +135 -0
- package/dist/collapsible.d.ts +54 -0
- package/dist/collapsible.js +18 -0
- package/dist/color-picker.d.ts +187 -0
- package/dist/color-picker.js +158 -0
- package/dist/color.d.ts +2 -0
- package/dist/color.js +21 -0
- package/dist/combobox.d.ts +517 -0
- package/dist/combobox.js +531 -0
- package/dist/command.d.ts +322 -0
- package/dist/command.js +345 -0
- package/dist/controller-target.d.ts +49 -0
- package/dist/controller-target.js +112 -0
- package/dist/create-parts.d.ts +126 -0
- package/dist/create-parts.js +206 -0
- package/dist/date-input.d.ts +133 -0
- package/dist/date-input.js +31 -0
- package/dist/date-picker.d.ts +328 -0
- package/dist/date-picker.js +354 -0
- package/dist/date.d.ts +2 -0
- package/dist/date.js +59 -0
- package/dist/dialog-parts.d.ts +259 -0
- package/dist/dialog-parts.js +300 -0
- package/dist/dialog.d.ts +251 -0
- package/dist/dialog.js +25 -0
- package/dist/drawer.d.ts +304 -0
- package/dist/drawer.js +373 -0
- package/dist/editor-context.d.ts +4 -0
- package/dist/editor-context.js +5 -0
- package/dist/field-context.d.ts +26 -0
- package/dist/field-context.js +44 -0
- package/dist/field.d.ts +154 -0
- package/dist/field.js +240 -0
- package/dist/file-input.d.ts +212 -0
- package/dist/file-input.js +255 -0
- package/dist/form-inputs.d.ts +16 -0
- package/dist/form-inputs.js +25 -0
- package/dist/gesture.d.ts +1 -0
- package/dist/gesture.js +15 -0
- package/dist/history.d.ts +2 -0
- package/dist/history.js +11 -0
- package/dist/hover-card.d.ts +137 -0
- package/dist/hover-card.js +153 -0
- package/dist/image-cropper.d.ts +74 -0
- package/dist/image-cropper.js +36 -0
- package/dist/image.d.ts +2 -0
- package/dist/image.js +5 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +60 -0
- package/dist/input.d.ts +120 -0
- package/dist/input.js +73 -0
- package/dist/item-icon.d.ts +34 -0
- package/dist/item-icon.js +63 -0
- package/dist/label.d.ts +16 -0
- package/dist/label.js +8 -0
- package/dist/lifecycle.d.ts +11 -0
- package/dist/lifecycle.js +91 -0
- package/dist/list-items.d.ts +38 -0
- package/dist/list-items.js +59 -0
- package/dist/list-text.d.ts +7 -0
- package/dist/list-text.js +15 -0
- package/dist/listbox.d.ts +273 -0
- package/dist/listbox.js +126 -0
- package/dist/menu.d.ts +678 -0
- package/dist/menu.js +607 -0
- package/dist/menubar-context.d.ts +5 -0
- package/dist/menubar-context.js +5 -0
- package/dist/menubar.d.ts +61 -0
- package/dist/menubar.js +48 -0
- package/dist/meter.d.ts +41 -0
- package/dist/meter.js +37 -0
- package/dist/navigation-menu.d.ts +228 -0
- package/dist/navigation-menu.js +195 -0
- package/dist/number-input.d.ts +176 -0
- package/dist/number-input.js +231 -0
- package/dist/number.d.ts +2 -0
- package/dist/number.js +39 -0
- package/dist/nuxt/entries.json +977 -0
- package/dist/nuxt/module.d.ts +18 -0
- package/dist/nuxt/module.js +31 -0
- package/dist/nuxt/registrations.d.ts +22 -0
- package/dist/nuxt/registrations.js +22 -0
- package/dist/observe.d.ts +2 -0
- package/dist/observe.js +7 -0
- package/dist/otp-input.d.ts +142 -0
- package/dist/otp-input.js +130 -0
- package/dist/overlays.d.ts +40 -0
- package/dist/overlays.js +118 -0
- package/dist/pagination.d.ts +201 -0
- package/dist/pagination.js +146 -0
- package/dist/password-input.d.ts +144 -0
- package/dist/password-input.js +134 -0
- package/dist/popover.d.ts +211 -0
- package/dist/popover.js +258 -0
- package/dist/popup.d.ts +60 -0
- package/dist/popup.js +89 -0
- package/dist/preferences.d.ts +44 -0
- package/dist/preferences.js +102 -0
- package/dist/primitive.d.ts +2 -0
- package/dist/primitive.js +11 -0
- package/dist/progress.d.ts +41 -0
- package/dist/progress.js +37 -0
- package/dist/radio.d.ts +164 -0
- package/dist/radio.js +145 -0
- package/dist/rating.d.ts +149 -0
- package/dist/rating.js +146 -0
- package/dist/renderless.d.ts +4 -0
- package/dist/renderless.js +28 -0
- package/dist/resizable.d.ts +54 -0
- package/dist/resizable.js +22 -0
- package/dist/scroll-area.d.ts +72 -0
- package/dist/scroll-area.js +77 -0
- package/dist/segmented-control.d.ts +151 -0
- package/dist/segmented-control.js +110 -0
- package/dist/segmented-input.d.ts +76 -0
- package/dist/segmented-input.js +115 -0
- package/dist/select.d.ts +458 -0
- package/dist/select.js +468 -0
- package/dist/separator.d.ts +14 -0
- package/dist/separator.js +8 -0
- package/dist/shortcuts.d.ts +32 -0
- package/dist/shortcuts.js +56 -0
- package/dist/slider.d.ts +65 -0
- package/dist/slider.js +194 -0
- package/dist/switch.d.ts +103 -0
- package/dist/switch.js +71 -0
- package/dist/tabs.d.ts +338 -0
- package/dist/tabs.js +27 -0
- package/dist/text-control.d.ts +89 -0
- package/dist/text-control.js +51 -0
- package/dist/text-match.d.ts +2 -0
- package/dist/text-match.js +7 -0
- package/dist/textarea.d.ts +106 -0
- package/dist/textarea.js +68 -0
- package/dist/time-input.d.ts +131 -0
- package/dist/time-input.js +32 -0
- package/dist/timeline.d.ts +81 -0
- package/dist/timeline.js +30 -0
- package/dist/toast-parts.d.ts +203 -0
- package/dist/toast-parts.js +270 -0
- package/dist/toast.d.ts +2 -0
- package/dist/toast.js +21 -0
- package/dist/toggle-props.d.ts +22 -0
- package/dist/toggle-props.js +14 -0
- package/dist/toggle.d.ts +199 -0
- package/dist/toggle.js +129 -0
- package/dist/token-input.d.ts +136 -0
- package/dist/token-input.js +214 -0
- package/dist/toolbar-context.d.ts +7 -0
- package/dist/toolbar-context.js +20 -0
- package/dist/toolbar.d.ts +120 -0
- package/dist/toolbar.js +77 -0
- package/dist/tooltip.d.ts +160 -0
- package/dist/tooltip.js +224 -0
- package/dist/tree.d.ts +300 -0
- package/dist/tree.js +209 -0
- package/dist/trigger-menu.d.ts +452 -0
- package/dist/trigger-menu.js +254 -0
- package/dist/use-primitive.d.ts +132 -0
- package/dist/use-primitive.js +462 -0
- package/dist/waterfall.d.ts +201 -0
- package/dist/waterfall.js +171 -0
- package/docs/README.md +89 -0
- package/docs/agents.md +1523 -0
- package/docs/components/accordion.md +1369 -0
- package/docs/components/alert-dialog.md +799 -0
- package/docs/components/autocomplete.md +1857 -0
- package/docs/components/avatar.md +272 -0
- package/docs/components/button.md +435 -0
- package/docs/components/calendar.md +1589 -0
- package/docs/components/carousel.md +1171 -0
- package/docs/components/checkbox.md +1008 -0
- package/docs/components/collapsible.md +536 -0
- package/docs/components/color-picker.md +706 -0
- package/docs/components/combobox.md +3504 -0
- package/docs/components/command.md +1481 -0
- package/docs/components/date-input.md +536 -0
- package/docs/components/date-picker.md +1244 -0
- package/docs/components/dialog.md +1397 -0
- package/docs/components/drawer.md +1166 -0
- package/docs/components/field.md +538 -0
- package/docs/components/file-input.md +611 -0
- package/docs/components/hover-card.md +505 -0
- package/docs/components/image-cropper.md +556 -0
- package/docs/components/input.md +306 -0
- package/docs/components/label.md +203 -0
- package/docs/components/listbox.md +1004 -0
- package/docs/components/menu.md +1229 -0
- package/docs/components/menubar.md +443 -0
- package/docs/components/meter.md +315 -0
- package/docs/components/navigation-menu.md +1523 -0
- package/docs/components/number-input.md +1029 -0
- package/docs/components/otp-input.md +512 -0
- package/docs/components/pagination.md +613 -0
- package/docs/components/password-input.md +649 -0
- package/docs/components/popover.md +1448 -0
- package/docs/components/preferences.md +208 -0
- package/docs/components/progress.md +423 -0
- package/docs/components/radio.md +785 -0
- package/docs/components/rating.md +416 -0
- package/docs/components/resizable.md +910 -0
- package/docs/components/scroll-area.md +915 -0
- package/docs/components/segmented-control.md +435 -0
- package/docs/components/select.md +2015 -0
- package/docs/components/separator.md +229 -0
- package/docs/components/slider.md +1084 -0
- package/docs/components/switch.md +565 -0
- package/docs/components/tabs.md +944 -0
- package/docs/components/textarea.md +299 -0
- package/docs/components/time-input.md +224 -0
- package/docs/components/timeline.md +654 -0
- package/docs/components/toast.md +1224 -0
- package/docs/components/toggle.md +500 -0
- package/docs/components/token-input.md +953 -0
- package/docs/components/toolbar.md +739 -0
- package/docs/components/tooltip.md +818 -0
- package/docs/components/tree.md +1306 -0
- package/docs/components/trigger-menu.md +1223 -0
- package/docs/components/waterfall.md +717 -0
- package/docs/controllers.md +71 -0
- package/docs/core.md +8027 -0
- package/docs/custom-parts.md +89 -0
- package/docs/first-component.md +83 -0
- package/docs/forms.md +122 -0
- package/docs/installation.md +72 -0
- package/docs/introduction.md +32 -0
- package/docs/lists-and-menus.md +122 -0
- package/docs/llms.txt +1502 -0
- package/docs/parts-and-composition.md +85 -0
- package/docs/popups.md +108 -0
- package/docs/rendering.md +56 -0
- package/docs/server-rendering.md +180 -0
- package/docs/state-and-events.md +100 -0
- package/docs/styling-and-animation.md +148 -0
- package/docs/utilities/async-options.md +348 -0
- package/docs/utilities/shortcuts.md +159 -0
- package/package.json +352 -3
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
# Radio
|
|
2
|
+
|
|
3
|
+
Choose one option, move the selection with arrow keys, and submit it with a form.
|
|
4
|
+
|
|
5
|
+
Use radios for a short list of choices where only one can be picked, like a shipping speed or a plan. The group keeps one value and gives the whole list a single tab stop.
|
|
6
|
+
|
|
7
|
+
```vue title="basic.vue"
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
10
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
11
|
+
|
|
12
|
+
const speeds = [
|
|
13
|
+
{ value: "standard", label: "Standard" },
|
|
14
|
+
{ value: "express", label: "Express" },
|
|
15
|
+
{ value: "overnight", label: "Overnight" },
|
|
16
|
+
]
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<Field>
|
|
21
|
+
<FieldLabel>Shipping speed</FieldLabel>
|
|
22
|
+
<RadioGroup default-value="standard">
|
|
23
|
+
<label v-for="speed in speeds" :key="speed.value" data-slot="label">
|
|
24
|
+
<Radio :value="speed.value">
|
|
25
|
+
<RadioIndicator />
|
|
26
|
+
</Radio>
|
|
27
|
+
{{ speed.label }}
|
|
28
|
+
</label>
|
|
29
|
+
</RadioGroup>
|
|
30
|
+
</Field>
|
|
31
|
+
</template>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Choose Express, then use the arrow keys to move the selection between speeds.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @ui-sdk/vue @ui-sdk/core
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Composition
|
|
43
|
+
|
|
44
|
+
Use the following composition to build a `RadioGroup`:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
RadioGroup
|
|
48
|
+
├── Radio
|
|
49
|
+
│ └── RadioIndicator
|
|
50
|
+
└── Radio
|
|
51
|
+
└── RadioIndicator
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Give each radio a `value` and wrap it with its text in a label. The group's value is the checked radio's value, and the indicator shows while its radio is checked.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
Import the parts and put them together:
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<script setup lang="ts">
|
|
62
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
63
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<Field>
|
|
68
|
+
<FieldLabel>Shipping speed</FieldLabel>
|
|
69
|
+
<RadioGroup default-value="standard">
|
|
70
|
+
<label>
|
|
71
|
+
<Radio value="standard">
|
|
72
|
+
<RadioIndicator />
|
|
73
|
+
</Radio>
|
|
74
|
+
Standard
|
|
75
|
+
</label>
|
|
76
|
+
<label>
|
|
77
|
+
<Radio value="express">
|
|
78
|
+
<RadioIndicator />
|
|
79
|
+
</Radio>
|
|
80
|
+
Express
|
|
81
|
+
</label>
|
|
82
|
+
</RadioGroup>
|
|
83
|
+
</Field>
|
|
84
|
+
</template>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The field's label names the group. Without a field, name it with `aria-label` or `aria-labelledby`. Without `default-value`, nothing starts checked and Tab enters on the first enabled radio.
|
|
88
|
+
|
|
89
|
+
## Description
|
|
90
|
+
|
|
91
|
+
Put each radio in its own `Field` with a `FieldLabel` and `FieldDescription` when an option needs more explanation. That field connects both to its radio, while the outer field still names the group.
|
|
92
|
+
|
|
93
|
+
```vue title="description.vue"
|
|
94
|
+
<script setup lang="ts">
|
|
95
|
+
import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
|
|
96
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
97
|
+
|
|
98
|
+
const frequencies = [
|
|
99
|
+
{ value: "instant", label: "Instantly", description: "An email right away." },
|
|
100
|
+
{
|
|
101
|
+
value: "daily",
|
|
102
|
+
label: "Daily digest",
|
|
103
|
+
description: "One email each morning.",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
value: "weekly",
|
|
107
|
+
label: "Weekly digest",
|
|
108
|
+
description: "A summary every Monday.",
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<Field>
|
|
115
|
+
<FieldLabel>Email notifications</FieldLabel>
|
|
116
|
+
<RadioGroup default-value="daily">
|
|
117
|
+
<Field v-for="frequency in frequencies" :key="frequency.value">
|
|
118
|
+
<Radio :value="frequency.value">
|
|
119
|
+
<RadioIndicator />
|
|
120
|
+
</Radio>
|
|
121
|
+
<div data-slot="stack">
|
|
122
|
+
<FieldLabel>{{ frequency.label }}</FieldLabel>
|
|
123
|
+
<FieldDescription>{{ frequency.description }}</FieldDescription>
|
|
124
|
+
</div>
|
|
125
|
+
</Field>
|
|
126
|
+
</RadioGroup>
|
|
127
|
+
</Field>
|
|
128
|
+
</template>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Choose Weekly digest by its label. Each radio is also described by the line under it.
|
|
132
|
+
|
|
133
|
+
## Choice Card
|
|
134
|
+
|
|
135
|
+
Wrap an option's content in its label to make the whole card clickable. `:has([data-checked])` lets the card reflect the radio inside it.
|
|
136
|
+
|
|
137
|
+
```vue title="choice-card.vue"
|
|
138
|
+
<script setup lang="ts">
|
|
139
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
140
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
141
|
+
|
|
142
|
+
const plans = [
|
|
143
|
+
{
|
|
144
|
+
value: "hobby",
|
|
145
|
+
label: "Hobby",
|
|
146
|
+
description: "Free for personal projects.",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
value: "pro",
|
|
150
|
+
label: "Pro",
|
|
151
|
+
description: "$20 per month for growing teams.",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
value: "enterprise",
|
|
155
|
+
label: "Enterprise",
|
|
156
|
+
description: "Custom pricing and support.",
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
</script>
|
|
160
|
+
|
|
161
|
+
<template>
|
|
162
|
+
<Field>
|
|
163
|
+
<FieldLabel>Plan</FieldLabel>
|
|
164
|
+
<RadioGroup default-value="hobby">
|
|
165
|
+
<label v-for="plan in plans" :key="plan.value" data-slot="card">
|
|
166
|
+
<span data-slot="stack">
|
|
167
|
+
<span>{{ plan.label }}</span>
|
|
168
|
+
<span data-slot="hint">{{ plan.description }}</span>
|
|
169
|
+
</span>
|
|
170
|
+
<Radio :value="plan.value">
|
|
171
|
+
<RadioIndicator />
|
|
172
|
+
</Radio>
|
|
173
|
+
</label>
|
|
174
|
+
</RadioGroup>
|
|
175
|
+
</Field>
|
|
176
|
+
</template>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Click anywhere on the Pro card to choose it. Arrow keys move the selection between cards too.
|
|
180
|
+
|
|
181
|
+
## Fieldset and Legend
|
|
182
|
+
|
|
183
|
+
Use `FieldSet` and `FieldLegend` to describe a set of choices together. Connect the group to the legend with `aria-labelledby`, and to the description with `aria-describedby`.
|
|
184
|
+
|
|
185
|
+
```vue title="legend.vue"
|
|
186
|
+
<script setup lang="ts">
|
|
187
|
+
import { FieldDescription, FieldLegend, FieldSet } from "@ui-sdk/vue/field"
|
|
188
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
189
|
+
import { useId } from "vue"
|
|
190
|
+
|
|
191
|
+
const periods = [
|
|
192
|
+
{ value: "monthly", label: "Monthly", price: "$12 per month" },
|
|
193
|
+
{ value: "yearly", label: "Yearly", price: "$120 per year" },
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
const id = useId()
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<template>
|
|
200
|
+
<FieldSet>
|
|
201
|
+
<FieldLegend :id="`${id}-legend`">Billing period</FieldLegend>
|
|
202
|
+
<FieldDescription :id="`${id}-description`"
|
|
203
|
+
>Yearly billing gets you two months free.</FieldDescription
|
|
204
|
+
>
|
|
205
|
+
<RadioGroup
|
|
206
|
+
default-value="monthly"
|
|
207
|
+
:aria-labelledby="`${id}-legend`"
|
|
208
|
+
:aria-describedby="`${id}-description`"
|
|
209
|
+
>
|
|
210
|
+
<label v-for="period in periods" :key="period.value" data-slot="label">
|
|
211
|
+
<Radio :value="period.value">
|
|
212
|
+
<RadioIndicator />
|
|
213
|
+
</Radio>
|
|
214
|
+
{{ period.label }}
|
|
215
|
+
<span data-slot="hint">({{ period.price }})</span>
|
|
216
|
+
</label>
|
|
217
|
+
</RadioGroup>
|
|
218
|
+
</FieldSet>
|
|
219
|
+
</template>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Choose Yearly. The legend and the line under it describe both billing periods.
|
|
223
|
+
|
|
224
|
+
## Disabled
|
|
225
|
+
|
|
226
|
+
Set `disabled` on the group or on one radio. Disabled radios can't be focused or checked, and arrow keys skip them. A disabled fieldset disables the group too, and a disabled group submits nothing.
|
|
227
|
+
|
|
228
|
+
```vue title="disabled.vue"
|
|
229
|
+
<script setup lang="ts">
|
|
230
|
+
import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
|
|
231
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
232
|
+
|
|
233
|
+
const themes = [
|
|
234
|
+
{ value: "light", label: "Light" },
|
|
235
|
+
{ value: "dark", label: "Dark" },
|
|
236
|
+
{ value: "system", label: "System" },
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
const backups = [
|
|
240
|
+
{ value: "hourly", label: "Hourly", disabled: true },
|
|
241
|
+
{ value: "daily", label: "Daily", disabled: false },
|
|
242
|
+
{ value: "weekly", label: "Weekly", disabled: false },
|
|
243
|
+
]
|
|
244
|
+
</script>
|
|
245
|
+
|
|
246
|
+
<template>
|
|
247
|
+
<div data-slot="stack">
|
|
248
|
+
<Field>
|
|
249
|
+
<FieldLabel>Theme</FieldLabel>
|
|
250
|
+
<FieldDescription>Set by your organization.</FieldDescription>
|
|
251
|
+
<RadioGroup default-value="light" disabled>
|
|
252
|
+
<label v-for="theme in themes" :key="theme.value" data-slot="label">
|
|
253
|
+
<Radio :value="theme.value">
|
|
254
|
+
<RadioIndicator />
|
|
255
|
+
</Radio>
|
|
256
|
+
{{ theme.label }}
|
|
257
|
+
</label>
|
|
258
|
+
</RadioGroup>
|
|
259
|
+
</Field>
|
|
260
|
+
<Field>
|
|
261
|
+
<FieldLabel>Backups</FieldLabel>
|
|
262
|
+
<FieldDescription>Hourly backups need the Pro plan.</FieldDescription>
|
|
263
|
+
<RadioGroup default-value="daily">
|
|
264
|
+
<label v-for="backup in backups" :key="backup.value" data-slot="label">
|
|
265
|
+
<Radio :value="backup.value" :disabled="backup.disabled">
|
|
266
|
+
<RadioIndicator />
|
|
267
|
+
</Radio>
|
|
268
|
+
{{ backup.label }}
|
|
269
|
+
</label>
|
|
270
|
+
</RadioGroup>
|
|
271
|
+
</Field>
|
|
272
|
+
</div>
|
|
273
|
+
</template>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The Theme group is disabled. In Backups, Hourly is disabled, so the arrow keys move between Daily and Weekly.
|
|
277
|
+
|
|
278
|
+
## Read-Only
|
|
279
|
+
|
|
280
|
+
Set `read-only` on the group to keep its value while still letting people focus and read the options. Arrow keys move focus without checking anything, and the value still submits with a form.
|
|
281
|
+
|
|
282
|
+
```vue title="read-only.vue"
|
|
283
|
+
<script setup lang="ts">
|
|
284
|
+
import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
|
|
285
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
286
|
+
|
|
287
|
+
const plans = [
|
|
288
|
+
{ value: "free", label: "Free" },
|
|
289
|
+
{ value: "pro", label: "Pro" },
|
|
290
|
+
{ value: "team", label: "Team" },
|
|
291
|
+
]
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<template>
|
|
295
|
+
<Field>
|
|
296
|
+
<FieldLabel>Your plan</FieldLabel>
|
|
297
|
+
<FieldDescription>Ask an admin to change plans.</FieldDescription>
|
|
298
|
+
<RadioGroup default-value="pro" read-only>
|
|
299
|
+
<label v-for="plan in plans" :key="plan.value" data-slot="label">
|
|
300
|
+
<Radio :value="plan.value">
|
|
301
|
+
<RadioIndicator />
|
|
302
|
+
</Radio>
|
|
303
|
+
{{ plan.label }}
|
|
304
|
+
</label>
|
|
305
|
+
</RadioGroup>
|
|
306
|
+
</Field>
|
|
307
|
+
</template>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Click Team, or focus Pro and press the Down arrow. Pro stays checked.
|
|
311
|
+
|
|
312
|
+
## Controlled
|
|
313
|
+
|
|
314
|
+
Use `v-model:value` to own the value, and `@value-change` for the reason. Call `details.cancel()` in `@value-change` to keep the current value. A [controller](#controller) from `createRadioGroupController()` checks a radio from anywhere, like the Choose Team button below. Create one controller per mounted group.
|
|
315
|
+
|
|
316
|
+
```vue title="controlled.vue"
|
|
317
|
+
<script setup lang="ts">
|
|
318
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
319
|
+
import {
|
|
320
|
+
createRadioGroupController,
|
|
321
|
+
Radio,
|
|
322
|
+
RadioGroup,
|
|
323
|
+
RadioIndicator,
|
|
324
|
+
} from "@ui-sdk/vue/radio"
|
|
325
|
+
import { ref } from "vue"
|
|
326
|
+
import { useLog } from "./log"
|
|
327
|
+
|
|
328
|
+
const plans = [
|
|
329
|
+
{ value: "starter", label: "Starter" },
|
|
330
|
+
{ value: "pro", label: "Pro" },
|
|
331
|
+
{ value: "team", label: "Team" },
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
const log = useLog()
|
|
335
|
+
const planGroup = createRadioGroupController()
|
|
336
|
+
const value = ref<string | null>("pro")
|
|
337
|
+
|
|
338
|
+
function onValueChange(next: string | null, details: { reason: string }) {
|
|
339
|
+
log(
|
|
340
|
+
`${plans.find((plan) => plan.value === next)?.label ?? "None"} (${details.reason})`
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
</script>
|
|
344
|
+
|
|
345
|
+
<template>
|
|
346
|
+
<div data-slot="stack">
|
|
347
|
+
<div data-slot="controls">
|
|
348
|
+
<button type="button" @click="planGroup.setValue('team')">
|
|
349
|
+
Choose Team
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
<Field>
|
|
353
|
+
<FieldLabel>Plan</FieldLabel>
|
|
354
|
+
<RadioGroup v-model:value="value" :controller="planGroup" @value-change="onValueChange">
|
|
355
|
+
<label v-for="plan in plans" :key="plan.value" data-slot="label">
|
|
356
|
+
<Radio :value="plan.value">
|
|
357
|
+
<RadioIndicator />
|
|
358
|
+
</Radio>
|
|
359
|
+
{{ plan.label }}
|
|
360
|
+
</label>
|
|
361
|
+
</RadioGroup>
|
|
362
|
+
</Field>
|
|
363
|
+
</div>
|
|
364
|
+
</template>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Choose Starter, then press Choose Team. The console logs each change and its reason.
|
|
368
|
+
|
|
369
|
+
See [State and Events](../state-and-events.md) for controlled state, cancellation and debugging.
|
|
370
|
+
|
|
371
|
+
## Form
|
|
372
|
+
|
|
373
|
+
Each radio renders a button with the `radio` role and a hidden native radio, so the group takes part in forms. Set `name` to submit the checked value; the group submits nothing without a name or a checked radio. `required` blocks submission until there's a choice, and a reset returns to `default-value`.
|
|
374
|
+
|
|
375
|
+
Set `form` to a form's id to join it from elsewhere on the page, as this group does.
|
|
376
|
+
|
|
377
|
+
```vue title="form.vue"
|
|
378
|
+
<script setup lang="ts">
|
|
379
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
380
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
381
|
+
import { useId } from "vue"
|
|
382
|
+
import { useLog } from "./log"
|
|
383
|
+
|
|
384
|
+
const speeds = [
|
|
385
|
+
{ value: "standard", label: "Standard", detail: "Free, 5 to 7 days" },
|
|
386
|
+
{ value: "express", label: "Express", detail: "$9, 2 days" },
|
|
387
|
+
{ value: "overnight", label: "Overnight", detail: "$24, next day" },
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
const log = useLog()
|
|
391
|
+
const id = useId()
|
|
392
|
+
|
|
393
|
+
function submit(event: Event) {
|
|
394
|
+
log(
|
|
395
|
+
`Submitted shipping=${new FormData(event.currentTarget as HTMLFormElement).get("shipping")}`
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
</script>
|
|
399
|
+
|
|
400
|
+
<template>
|
|
401
|
+
<div data-slot="stack">
|
|
402
|
+
<Field>
|
|
403
|
+
<FieldLabel>Shipping speed</FieldLabel>
|
|
404
|
+
<RadioGroup name="shipping" :form="`${id}-checkout`" required default-value="standard">
|
|
405
|
+
<label v-for="speed in speeds" :key="speed.value" data-slot="label">
|
|
406
|
+
<Radio :value="speed.value">
|
|
407
|
+
<RadioIndicator />
|
|
408
|
+
</Radio>
|
|
409
|
+
{{ speed.label }}
|
|
410
|
+
<span data-slot="hint">({{ speed.detail }})</span>
|
|
411
|
+
</label>
|
|
412
|
+
</RadioGroup>
|
|
413
|
+
</Field>
|
|
414
|
+
<form :id="`${id}-checkout`" data-slot="row" @submit.prevent="submit">
|
|
415
|
+
<button type="submit" data-variant="solid">Continue</button>
|
|
416
|
+
<button type="reset">Reset</button>
|
|
417
|
+
</form>
|
|
418
|
+
</div>
|
|
419
|
+
</template>
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Choose Express and press Continue. The console logs the submitted value, and Reset returns the choice to Standard.
|
|
423
|
+
|
|
424
|
+
## Validation
|
|
425
|
+
|
|
426
|
+
Put a required group in a [Field](./field.md) with a `FieldError`. Submitting without a choice shows the error under the group and moves focus to it. The [Forms guide](../forms.md) covers the shared field behavior.
|
|
427
|
+
|
|
428
|
+
```vue title="invalid.vue"
|
|
429
|
+
<script setup lang="ts">
|
|
430
|
+
import {
|
|
431
|
+
Field,
|
|
432
|
+
FieldDescription,
|
|
433
|
+
FieldError,
|
|
434
|
+
FieldLabel,
|
|
435
|
+
} from "@ui-sdk/vue/field"
|
|
436
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
437
|
+
import { useLog } from "./log"
|
|
438
|
+
|
|
439
|
+
const channels = [
|
|
440
|
+
{ value: "email", label: "Email only" },
|
|
441
|
+
{ value: "sms", label: "SMS only" },
|
|
442
|
+
{ value: "both", label: "Email and SMS" },
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
const log = useLog()
|
|
446
|
+
|
|
447
|
+
function submit(event: Event) {
|
|
448
|
+
log(
|
|
449
|
+
`Saved updates=${new FormData(event.currentTarget as HTMLFormElement).get("updates")}`
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
</script>
|
|
453
|
+
|
|
454
|
+
<template>
|
|
455
|
+
<form data-slot="stack" @submit.prevent="submit">
|
|
456
|
+
<Field>
|
|
457
|
+
<FieldLabel>Order updates</FieldLabel>
|
|
458
|
+
<FieldDescription
|
|
459
|
+
>Choose how we tell you about your orders.</FieldDescription
|
|
460
|
+
>
|
|
461
|
+
<RadioGroup name="updates" required>
|
|
462
|
+
<label v-for="channel in channels" :key="channel.value" data-slot="label">
|
|
463
|
+
<Radio :value="channel.value">
|
|
464
|
+
<RadioIndicator />
|
|
465
|
+
</Radio>
|
|
466
|
+
{{ channel.label }}
|
|
467
|
+
</label>
|
|
468
|
+
</RadioGroup>
|
|
469
|
+
<FieldError>Choose how to get order updates.</FieldError>
|
|
470
|
+
</Field>
|
|
471
|
+
<div data-slot="row">
|
|
472
|
+
<button type="submit" data-variant="solid">Save</button>
|
|
473
|
+
</div>
|
|
474
|
+
</form>
|
|
475
|
+
</template>
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Press Save without a choice to see the error. Choose SMS only and save again; the console logs the saved value.
|
|
479
|
+
|
|
480
|
+
## Animation
|
|
481
|
+
|
|
482
|
+
The indicator has `data-starting-style` as it appears and `data-ending-style` while it leaves, and stays mounted until its exit finishes. Transition from those states:
|
|
483
|
+
|
|
484
|
+
```css
|
|
485
|
+
[data-slot="radio-indicator"] {
|
|
486
|
+
transition:
|
|
487
|
+
opacity 0.15s,
|
|
488
|
+
scale 0.15s;
|
|
489
|
+
|
|
490
|
+
&:is([data-starting-style], [data-ending-style]) {
|
|
491
|
+
opacity: 0;
|
|
492
|
+
scale: 0.5;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
`keep-mounted` keeps the indicator in the page while unchecked, with `data-unchecked`. Here it lets a hovered option preview its dot.
|
|
498
|
+
|
|
499
|
+
```vue title="indicator.vue"
|
|
500
|
+
<script setup lang="ts">
|
|
501
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
502
|
+
import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/vue/radio"
|
|
503
|
+
|
|
504
|
+
const themes = [
|
|
505
|
+
{ value: "light", label: "Light" },
|
|
506
|
+
{ value: "dark", label: "Dark" },
|
|
507
|
+
{ value: "system", label: "System" },
|
|
508
|
+
]
|
|
509
|
+
</script>
|
|
510
|
+
|
|
511
|
+
<template>
|
|
512
|
+
<Field>
|
|
513
|
+
<FieldLabel>Theme</FieldLabel>
|
|
514
|
+
<RadioGroup default-value="system">
|
|
515
|
+
<label v-for="theme in themes" :key="theme.value" data-slot="label">
|
|
516
|
+
<Radio :value="theme.value">
|
|
517
|
+
<RadioIndicator keep-mounted />
|
|
518
|
+
</Radio>
|
|
519
|
+
{{ theme.label }}
|
|
520
|
+
</label>
|
|
521
|
+
</RadioGroup>
|
|
522
|
+
</Field>
|
|
523
|
+
</template>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Hover Light to preview its dot, then choose it. The dot scales in on Light and out of System.
|
|
527
|
+
|
|
528
|
+
## Loop
|
|
529
|
+
|
|
530
|
+
Arrow keys wrap from the last radio to the first. Set `loop` to `false` to stop at the ends.
|
|
531
|
+
|
|
532
|
+
```vue
|
|
533
|
+
<RadioGroup default-value="low" :loop="false">
|
|
534
|
+
...
|
|
535
|
+
</RadioGroup>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Custom Rendering
|
|
539
|
+
|
|
540
|
+
The group, radio and indicator take a `render` slot, which receives the props to bind and the part's state. Keep the part's props on the interactive element so its role, label and keyboard behavior stay connected. See [Parts and Composition](../parts-and-composition.md) for the rendering contract.
|
|
541
|
+
|
|
542
|
+
```vue title="render.vue"
|
|
543
|
+
<script setup lang="ts">
|
|
544
|
+
import { CheckIcon } from "@lucide/vue"
|
|
545
|
+
import { Field, FieldLabel } from "@ui-sdk/vue/field"
|
|
546
|
+
import { Radio, RadioGroup } from "@ui-sdk/vue/radio"
|
|
547
|
+
import { useId } from "vue"
|
|
548
|
+
|
|
549
|
+
const options = [
|
|
550
|
+
{
|
|
551
|
+
value: "all",
|
|
552
|
+
label: "Everything",
|
|
553
|
+
description: "Every message and update.",
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
value: "mentions",
|
|
557
|
+
label: "Mentions",
|
|
558
|
+
description: "Only when someone mentions you.",
|
|
559
|
+
},
|
|
560
|
+
{ value: "none", label: "Nothing", description: "Turn notifications off." },
|
|
561
|
+
]
|
|
562
|
+
|
|
563
|
+
const id = useId()
|
|
564
|
+
</script>
|
|
565
|
+
|
|
566
|
+
<template>
|
|
567
|
+
<Field>
|
|
568
|
+
<FieldLabel>Notify me about</FieldLabel>
|
|
569
|
+
<RadioGroup default-value="mentions">
|
|
570
|
+
<Radio
|
|
571
|
+
v-for="option in options"
|
|
572
|
+
:key="option.value"
|
|
573
|
+
:value="option.value"
|
|
574
|
+
:aria-labelledby="`${id}-${option.value}-label`"
|
|
575
|
+
:aria-describedby="`${id}-${option.value}-description`"
|
|
576
|
+
>
|
|
577
|
+
<template #render="{ props, state }">
|
|
578
|
+
<button v-bind="props">
|
|
579
|
+
<span data-slot="stack">
|
|
580
|
+
<span :id="`${id}-${option.value}-label`">{{
|
|
581
|
+
option.label
|
|
582
|
+
}}</span>
|
|
583
|
+
<span
|
|
584
|
+
:id="`${id}-${option.value}-description`"
|
|
585
|
+
data-slot="hint"
|
|
586
|
+
>{{ option.description }}</span
|
|
587
|
+
>
|
|
588
|
+
</span>
|
|
589
|
+
<CheckIcon aria-hidden="true" :data-checked="state.checked ? '' : undefined" />
|
|
590
|
+
</button>
|
|
591
|
+
</template>
|
|
592
|
+
</Radio>
|
|
593
|
+
</RadioGroup>
|
|
594
|
+
</Field>
|
|
595
|
+
</template>
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Choose Nothing. Each radio renders a full-width button that shows a check from its state.
|
|
599
|
+
|
|
600
|
+
## Styling
|
|
601
|
+
|
|
602
|
+
Use `data-checked` and `data-unchecked` for the checked state, and `data-disabled`, `data-readonly` and `data-required` for the rest. The indicator gets the same state attributes as its radio, and the group gets `data-disabled`, `data-readonly` and `data-required`.
|
|
603
|
+
|
|
604
|
+
Each part has a `data-slot`, such as `radio`, and its state attributes are listed under [Data Attributes](#data-attributes).
|
|
605
|
+
|
|
606
|
+
## Data Attributes
|
|
607
|
+
|
|
608
|
+
### RadioGroup
|
|
609
|
+
`[data-slot="radio-group"]`
|
|
610
|
+
|
|
611
|
+
- `data-readonly`: Present when the group is read-only.
|
|
612
|
+
- `data-required`: Present when the group is required.
|
|
613
|
+
- `data-disabled`: Present when disabled, by its own prop, the group or a `<fieldset>`.
|
|
614
|
+
|
|
615
|
+
### Radio
|
|
616
|
+
`[data-slot="radio"]`
|
|
617
|
+
|
|
618
|
+
- `data-checked`: Present on the checked radio.
|
|
619
|
+
- `data-readonly`: Present when the group is read-only.
|
|
620
|
+
- `data-required`: Present when the group is required.
|
|
621
|
+
- `data-unchecked`: Present on radios that aren't checked.
|
|
622
|
+
- `data-disabled`: Present when disabled, by its own prop, the group or a `<fieldset>`.
|
|
623
|
+
|
|
624
|
+
### RadioIndicator
|
|
625
|
+
`[data-slot="radio-indicator"]`
|
|
626
|
+
|
|
627
|
+
- `data-checked`: Present on the checked radio.
|
|
628
|
+
- `data-readonly`: Present when the group is read-only.
|
|
629
|
+
- `data-required`: Present when the group is required.
|
|
630
|
+
- `data-unchecked`: Present on radios that aren't checked.
|
|
631
|
+
- `data-disabled`: Present when disabled, by its own prop, the group or a `<fieldset>`.
|
|
632
|
+
- `data-starting-style`: Present for the indicator's first frame. Style the state you animate from.
|
|
633
|
+
- `data-ending-style`: Present while the indicator animates out. Style the state you animate to.
|
|
634
|
+
|
|
635
|
+
## API Reference
|
|
636
|
+
|
|
637
|
+
Props are the same as below. `onValueChange` is `v-model:value`, or `@value-change` for the reason.
|
|
638
|
+
|
|
639
|
+
### RadioGroup
|
|
640
|
+
|
|
641
|
+
- `value?` (`T | null`): The checked radio's value. Pass it to control the group. `null` checks nothing.
|
|
642
|
+
- `defaultValue?` (`T | null`): The radio checked at first, when uncontrolled. A form reset returns to it.
|
|
643
|
+
- `@value-change` (`((value: T | null, details: ChangeDetails<RadioGroupValueChangeReason>) => void)`): Runs when a user or a form reset wants to change the value. `details.reason` says why, and `details.cancel()` stops it.
|
|
644
|
+
- `disabled?` (`boolean`): Disables every radio like native ones: they can't be focused or changed, and the value isn't submitted.
|
|
645
|
+
- `readOnly?` (`boolean`): Keeps the items focusable and the value submitted, but stops changes.
|
|
646
|
+
- `required?` (`boolean`): Blocks form submission until an item is checked, with the browser's own message.
|
|
647
|
+
- `name?` (`string`): The name the value submits under. Without one, the group submits nothing.
|
|
648
|
+
- `form?` (`string`): The id of a form to belong to, when the group isn't inside that form.
|
|
649
|
+
- `loop?` (`boolean`): Whether arrow keys wrap from the last item to the first.
|
|
650
|
+
- `controller?` (`{ getValue: () => T | null; setValue: (value: T | null, details?: Details) => boolean; focus: () => void; }`): Checks radios from anywhere. Create one with `createRadioGroupController()`.
|
|
651
|
+
- `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
|
|
652
|
+
|
|
653
|
+
### Radio
|
|
654
|
+
|
|
655
|
+
- `value` (`string`): What the group's value becomes when this item is checked.
|
|
656
|
+
- `disabled?` (`boolean`): Skips the item, like a disabled native radio: arrow keys pass it and it can't be focused or checked.
|
|
657
|
+
|
|
658
|
+
### RadioIndicator
|
|
659
|
+
|
|
660
|
+
- `keepMounted?` (`boolean`): Whether the indicator stays in the page while its item is unchecked.
|
|
661
|
+
|
|
662
|
+
### State
|
|
663
|
+
|
|
664
|
+
What render functions, state-based class names and slots receive.
|
|
665
|
+
|
|
666
|
+
- `value` (`string | null`): The checked item's value.
|
|
667
|
+
- `disabled` (`boolean`): Whether the group is disabled, by itself or by a `<fieldset>`.
|
|
668
|
+
- `readOnly` (`boolean`): Whether the group is read-only: arrow keys still move focus, but nothing changes the checked item.
|
|
669
|
+
- `required` (`boolean`): Whether a form won't submit until an item is checked.
|
|
670
|
+
|
|
671
|
+
- `checked` (`boolean`): Whether this item is checked.
|
|
672
|
+
- `disabled` (`boolean`): Whether this item is disabled, by itself, its group or a `<fieldset>`.
|
|
673
|
+
- `readOnly` (`boolean`): Whether the group is read-only.
|
|
674
|
+
- `required` (`boolean`): Whether the group is required.
|
|
675
|
+
|
|
676
|
+
- `checked` (`boolean`): Whether this item is checked.
|
|
677
|
+
- `disabled` (`boolean`): Whether this item is disabled, by itself, its group or a `<fieldset>`.
|
|
678
|
+
- `readOnly` (`boolean`): Whether the group is read-only.
|
|
679
|
+
- `required` (`boolean`): Whether the group is required.
|
|
680
|
+
|
|
681
|
+
### Reasons
|
|
682
|
+
|
|
683
|
+
`details.reason` says why the checked value changed.
|
|
684
|
+
|
|
685
|
+
- `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
|
|
686
|
+
- `list-navigation` (`KeyboardEvent`): Arrow, Home or End keys moved the selection, typing picked an item, or Ctrl or ⌘ with A selected every item of a listbox.
|
|
687
|
+
- `reset` (`none`): A form reset.
|
|
688
|
+
- `restore` (`none`): The browser restored state, like after going back, or a saved value was read back from storage.
|
|
689
|
+
- `imperative` (`none`): A controller or `api` call, or a property set on a web component.
|
|
690
|
+
|
|
691
|
+
### Controller
|
|
692
|
+
|
|
693
|
+
Create one with `createRadioGroupController()` and pass it as `controller`.
|
|
694
|
+
|
|
695
|
+
Every write method takes an optional `{ reason, event }`, runs the change callback like a press, and returns `false` when the change is canceled.
|
|
696
|
+
|
|
697
|
+
- `getValue()` (`() => T | null`): The checked radio's value.
|
|
698
|
+
- `setValue(value, details?)` (`(value: T | null, details?: RadioGroupChangeOptions | undefined) => boolean`): Checks a radio, as a user would. Disabled radios and read-only groups ignore it.
|
|
699
|
+
- `focus()` (`() => void`): Moves focus to the checked radio, or the first one.
|
|
700
|
+
|
|
701
|
+
### Events
|
|
702
|
+
|
|
703
|
+
Events get the new value first, then the details. `v-model` only updates when the change event wasn't canceled.
|
|
704
|
+
|
|
705
|
+
| Binding | On | Receives |
|
|
706
|
+
| --------------- | ------------ | ----------------------------------------------------------------- |
|
|
707
|
+
| `v-model:value` | `RadioGroup` | The checked value |
|
|
708
|
+
| `@value-change` | `RadioGroup` | `(value, details)`: the checked value, and the [reason](#reasons) |
|
|
709
|
+
|
|
710
|
+
## Accessibility
|
|
711
|
+
|
|
712
|
+
Radio groups follow the [WAI-ARIA radio group pattern](https://www.w3.org/WAI/ARIA/apg/patterns/radio/), and behave like native radios that share a name. `Radio` renders a button with the `radio` role, and a hidden native radio that takes part in forms.
|
|
713
|
+
|
|
714
|
+
### Keyboard
|
|
715
|
+
|
|
716
|
+
| Key | Does |
|
|
717
|
+
| ---------- | ----------------------------------------------------------------------- |
|
|
718
|
+
| Tab | Moves into the group, onto the checked radio or the first enabled one |
|
|
719
|
+
| Arrow keys | Move to the next or previous radio and check it, skipping disabled ones |
|
|
720
|
+
| Space | Checks the focused radio |
|
|
721
|
+
| Enter | Submits its form |
|
|
722
|
+
|
|
723
|
+
### Tested Behavior
|
|
724
|
+
|
|
725
|
+
Every line below is an automated test.
|
|
726
|
+
|
|
727
|
+
**Selection**
|
|
728
|
+
|
|
729
|
+
- Nothing is checked at first without a default value.
|
|
730
|
+
- Clicking an item checks it and unchecks the others.
|
|
731
|
+
- Clicking an item's label checks it.
|
|
732
|
+
- Clicking a disabled item does nothing.
|
|
733
|
+
- A change can be cancelled.
|
|
734
|
+
|
|
735
|
+
**Keyboard**
|
|
736
|
+
|
|
737
|
+
- Tab lands on the first item when nothing is checked.
|
|
738
|
+
- Tab lands on the checked item.
|
|
739
|
+
- Tab leaves the group after one stop.
|
|
740
|
+
- Space checks the focused item.
|
|
741
|
+
- ArrowDown and ArrowRight move to the next item and check it.
|
|
742
|
+
- ArrowUp and ArrowLeft move to the previous item and check it.
|
|
743
|
+
- Arrow keys skip disabled items.
|
|
744
|
+
- Arrow keys wrap at both ends.
|
|
745
|
+
- Enter never checks an item.
|
|
746
|
+
- Keys pressed in quick succession are never lost.
|
|
747
|
+
- Enter on an item submits the form.
|
|
748
|
+
|
|
749
|
+
**Read-only and disabled**
|
|
750
|
+
|
|
751
|
+
- A read-only group moves focus with arrow keys but checks nothing.
|
|
752
|
+
- A disabled group can't be focused or changed.
|
|
753
|
+
- Clicking a radio in a disabled group doesn't focus it.
|
|
754
|
+
- Clicking the label of a radio in a disabled group neither focuses nor checks it.
|
|
755
|
+
|
|
756
|
+
**Animation**
|
|
757
|
+
|
|
758
|
+
- The indicator animates in from its starting style.
|
|
759
|
+
|
|
760
|
+
Runs in React, Vue and web components.
|
|
761
|
+
|
|
762
|
+
In a field:
|
|
763
|
+
|
|
764
|
+
**With every radio enabled**
|
|
765
|
+
|
|
766
|
+
- Each radio is named by the label of its own field.
|
|
767
|
+
- The field around the group still names the group.
|
|
768
|
+
- Clicking a radio's label checks it and unchecks the others.
|
|
769
|
+
- The group keeps one tab stop.
|
|
770
|
+
- Arrow keys move between the radios and check them.
|
|
771
|
+
|
|
772
|
+
**A disabled radio**
|
|
773
|
+
|
|
774
|
+
- A disabled field around the first radio moves the tab stop to the next.
|
|
775
|
+
- Disabling the first radio after it renders moves the tab stop to the next.
|
|
776
|
+
- Enabling the first radio again gives it back the tab stop.
|
|
777
|
+
|
|
778
|
+
Runs in React, Vue and web components.
|
|
779
|
+
|
|
780
|
+
In right-to-left pages:
|
|
781
|
+
|
|
782
|
+
- ArrowLeft moves to the next radio and ArrowRight to the previous one.
|
|
783
|
+
- ArrowDown and ArrowUp keep their direction.
|
|
784
|
+
|
|
785
|
+
Runs in React, Vue and web components.
|