Components
Form
A way to build structured forms.
Usage
---import { Form } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { FieldDescription } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend><!-- --></FieldLegend> <FieldDescription><!-- --></FieldDescription> <FieldGroup> <!-- --> </FieldGroup> <FieldGroup> <!-- --> </FieldGroup> </FieldSet></Form>Non-semantic elements
Sometimes you can't use semantic form elements like <fieldset> and <legend>. Use the as prop on FieldSet and FieldLegend to render non-semantic alternatives.
<Form as="div"> <FieldSet as="div"> <FieldLegend as="p">Using as prop</FieldLegend> <FieldDescription> Renders as div and p elements. </FieldDescription> </FieldSet></Form>Fieldset
Used to show a relationship between form elements.
-
FieldLegend - to describe what it's about.
-
FieldDescription(optional) - to give extra context about the fieldset.
-
FieldGroup - groups related fields.
---import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldDescription } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Radio } from "@opui/astro"---
<FieldSet> <FieldLegend>Favorite Pet</FieldLegend> <FieldDescription>Please select your favorite type of pet.</FieldDescription> <FieldGroup name="pet"> <Radio value="dog">Dog</Radio> <Radio value="cat">Cat</Radio> <Radio value="hamster">Hamster</Radio> </FieldGroup></FieldSet><fieldset class="fieldset"> <legend>Favorite Pet</legend> <p class="field-description">Please select your favorite type of pet.</p> <div class="field-group" role="group"> <label class="radio"> <input type="radio" name="pet" value="dog" /><span class="label" >Dog</span > </label> <label class="radio"> <input type="radio" name="pet" value="cat" /><span class="label" >Cat</span > </label> <label class="radio"> <input type="radio" name="pet" value="hamster" /><span class="label" >Hamster</span > </label> </div></fieldset>Required
---import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldDescription } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Textarea } from "@opui/astro"import { TextField } from "@opui/astro"---
<FieldSet> <FieldLegend>Pet info</FieldLegend> <FieldDescription>We must know your pet's information.</FieldDescription> <FieldGroup name="bio"> <TextField label="Name" /> <Textarea required label="Life story" /> </FieldGroup></FieldSet><fieldset class="fieldset"> <legend>Pet info</legend> <p class="field-description">We must know your pet's information.</p> <div class="field-group" role="group"> <label class="text-field"> <span class="label">Name</span> <span class="field"> <input id="text-field-2" name="bio" type="text" /> </span> </label> <label class="textarea"> <span class="label">Life story</span> <span class="field"> <textarea id="textarea-1" name="bio" required></textarea> </span> </label> </div></fieldset>Disabled
Turns out you can disable an entire fieldset.
---import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldDescription } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Checkbox } from "@opui/astro"---
<FieldSet disabled> <FieldLegend>Pet dating</FieldLegend> <FieldDescription>You can't change these settings</FieldDescription> <FieldGroup name="notifications"> <Checkbox value="horse-tinder" checked>Horse Tinder</Checkbox> <Checkbox value="onlyhorsefans" checked>OnlyHorseFans</Checkbox> </FieldGroup></FieldSet><fieldset disabled class="fieldset"> <legend>Pet dating</legend> <p class="field-description">You can't change these settings</p> <div class="field-group" role="group"> <script type="module"> function e(t = document) { t.querySelectorAll( 'input[type="checkbox"][data-indeterminate]', ).forEach((n) => { n.indeterminate = !0; }); } function a() { (e(), document.addEventListener("astro:after-swap", () => e())); } a(); </script> <label class="checkbox"> <input type="checkbox" checked name="notifications" value="horse-tinder" /><span class="label">Horse Tinder</span> </label> <label class="checkbox"> <input type="checkbox" checked name="notifications" value="onlyhorsefans" /><span class="label">OnlyHorseFans</span> </label> </div></fieldset>Field Legend
Use FieldLegend (or <legend>) to describe
the fieldset.
---import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"---
<FieldSet> <FieldLegend>Legend</FieldLegend></FieldSet><fieldset class="fieldset"><legend>Legend</legend></fieldset>Field Description
Use FieldDescription (or .field-description) to
give extra context about the fieldset.
---import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldDescription } from "@opui/astro"---
<FieldSet> <FieldLegend>Legend</FieldLegend> <FieldDescription>This is a field description.</FieldDescription></FieldSet><fieldset class="fieldset"> <legend>Legend</legend> <p class="field-description">This is a field description.</p></fieldset>Field Group
Use FieldGroup to wrap related fields. It provides a shared
name to all nested inputs.
---import { Checkbox } from "@opui/astro"import { FieldDescription } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldSet } from "@opui/astro"import { Form } from "@opui/astro"import { Radio } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>Choose your favorite Radiohead album</FieldLegend> <FieldDescription>There are no wrong answers.</FieldDescription> <FieldGroup name="albums"> <Radio value="ok-computer">OK Computer</Radio> <Radio value="kid-a">Kid A</Radio> <Radio value="in-rainbows">In Rainbows</Radio> <Radio value="king-of-limbs">The King of Limbs</Radio> </FieldGroup> </FieldSet>
<FieldSet> <FieldLegend>Which side projects do you follow?</FieldLegend> <FieldDescription>Some are better than others.</FieldDescription> <FieldGroup name="projects"> <Checkbox value="the-smile"> The Smile <Fragment slot="end-text" >Thom Yorke, Jonny Greenwood, Tom Skinner</Fragment > </Checkbox> <Checkbox value="atoms-for-peace"> Atoms for Peace <Fragment slot="end-text">Thom Yorke, Flea, Nigel Godrich</Fragment> </Checkbox> <Checkbox value="eob"> EOB <Fragment slot="end-text">Ed O'Brien solo</Fragment> </Checkbox> <Checkbox value="jonny-scores"> Film Scores <Fragment slot="end-text">Film compositions by Jonny Greenwood</Fragment > </Checkbox> <Checkbox value="selway-solo"> Philip Selway <Fragment slot="end-text">Philip Selway solo albums</Fragment> </Checkbox> </FieldGroup> </FieldSet></Form><form class="form"> <fieldset class="fieldset"> <legend>Choose your favorite Radiohead album</legend> <p class="field-description">There are no wrong answers.</p> <div class="field-group" role="group"> <label class="radio"> <input type="radio" name="albums" value="ok-computer" /><span class="label" >OK Computer</span > </label> <label class="radio"> <input type="radio" name="albums" value="kid-a" /><span class="label" >Kid A</span > </label> <label class="radio"> <input type="radio" name="albums" value="in-rainbows" /><span class="label" >In Rainbows</span > </label> <label class="radio"> <input type="radio" name="albums" value="king-of-limbs" /><span class="label" >The King of Limbs</span > </label> </div> </fieldset> <fieldset class="fieldset"> <legend>Which side projects do you follow?</legend> <p class="field-description">Some are better than others.</p> <div class="field-group" role="group"> <label class="checkbox"> <input type="checkbox" name="projects" value="the-smile" aria-describedby="end-text-1" /><span class="label"> The Smile </span> <span id="end-text-1" class="end-text" >Thom Yorke, Jonny Greenwood, Tom Skinner</span > </label> <label class="checkbox"> <input type="checkbox" name="projects" value="atoms-for-peace" aria-describedby="end-text-2" /><span class="label"> Atoms for Peace </span> <span id="end-text-2" class="end-text" >Thom Yorke, Flea, Nigel Godrich</span > </label> <label class="checkbox"> <input type="checkbox" name="projects" value="eob" aria-describedby="end-text-3" /><span class="label"> EOB </span> <span id="end-text-3" class="end-text">Ed O'Brien solo</span> </label> <label class="checkbox"> <input type="checkbox" name="projects" value="jonny-scores" aria-describedby="end-text-4" /><span class="label"> Film Scores </span> <span id="end-text-4" class="end-text" >Film compositions by Jonny Greenwood</span > </label> <label class="checkbox"> <input type="checkbox" name="projects" value="selway-solo" aria-describedby="end-text-5" /><span class="label"> Philip Selway </span> <span id="end-text-5" class="end-text">Philip Selway solo albums</span> </label> </div> </fieldset></form>Row
Use the direction="row" prop to lay out fields horizontally.
---import { Form } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Checkbox } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>Options</FieldLegend> <FieldGroup direction="row"> <Checkbox>Option 1</Checkbox> <Checkbox>Option 2</Checkbox> <Checkbox>Option 3</Checkbox> </FieldGroup> </FieldSet></Form><form class="form"> <fieldset class="fieldset"> <legend>Options</legend> <div class="field-group row" role="group"> <label class="checkbox"> <input type="checkbox" name="projects" /><span class="label" >Option 1</span > </label> <label class="checkbox"> <input type="checkbox" name="projects" /><span class="label" >Option 2</span > </label> <label class="checkbox"> <input type="checkbox" name="projects" /><span class="label" >Option 3</span > </label> </div> </fieldset></form>Divider
Use the Divider component to create a visual break between sections
of your form.
---import { Form } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { TextField } from "@opui/astro"import { Button } from "@opui/astro"import { Divider } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>Post Content</FieldLegend> <FieldGroup> <TextField label="Title" placeholder="My new post" /> </FieldGroup> </FieldSet>
<Divider />
<FieldGroup> <Button variant="filled">Publish</Button> </FieldGroup></Form><form class="form"> <fieldset class="fieldset"> <legend>Post Content</legend> <div class="field-group" role="group"> <label class="text-field"> <span class="label">Title</span> <span class="field"> <input id="text-field-3" name="projects" placeholder="My new post" type="text" /> </span> </label> </div> </fieldset> <hr class="divider" /> <div class="field-group" role="group"> <button class="button filled">Publish</button> </div></form>Kitchen Sink
Everything all at once.
---import { Form } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldDescription } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { TextField } from "@opui/astro"import { Select } from "@opui/astro"import { Switch } from "@opui/astro"import { Radio } from "@opui/astro"import { Textarea } from "@opui/astro"import { Checkbox } from "@opui/astro"import { Range } from "@opui/astro"import { Button } from "@opui/astro"import { Divider } from "@opui/astro"---
<Form id="kitchen-sink-example"> <FieldSet> <FieldLegend>User Profile</FieldLegend> <FieldDescription> Please provide your basic contact details. </FieldDescription> <FieldGroup> <TextField label="Full Name" placeholder="Jane Doe" required /> <TextField label="Email Address" type="email" placeholder="jane@example.com" required /> <Select label="Role" items={[ { text: "Developer", value: "dev" }, { text: "Designer", value: "design" }, { text: "Manager", value: "manager" }, ]} /> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Notifications</FieldLegend> <FieldDescription> Configure how you want to receive updates. </FieldDescription> <FieldGroup name="notifications"> <Switch name="email_notifs" checked>Email Notifications</Switch> <Switch name="sms_notifs">SMS Notifications</Switch> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Theme Preference</FieldLegend> <FieldDescription>Select your preferred visual style.</FieldDescription> <FieldGroup name="theme"> <Radio value="light" checked>Light Theme</Radio> <Radio value="dark">Dark Theme</Radio> <Radio value="system">System Default</Radio> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Experience Level</FieldLegend> <FieldDescription >How many years of experience do you have?</FieldDescription > <FieldGroup> <Range label="Professional Experience" min="0" max="20" step="1" value="5" startText="Drag the slider to match your total tenure." /> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Additional Info</FieldLegend> <FieldDescription>Anything else we should know?</FieldDescription> <FieldGroup name="details"> <Textarea label="Biography" placeholder="Tell us about yourself..." rows={4} /> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Legal</FieldLegend> <FieldGroup name="legal"> <Checkbox name="terms" required> I agree to the terms and conditions <Fragment slot="end-text">Support this text</Fragment> </Checkbox> </FieldGroup> </FieldSet>
<Divider />
<FieldGroup> <Button variant="filled" type="submit">Send</Button> <Button>Cancel</Button> </FieldGroup></Form><form id="kitchen-sink-example" class="form"> <fieldset class="fieldset"><legend>User Profile</legend><p class="field-description">Please provide your basic contact details.</p> <div class="field-group" role="group"> <label class="text-field"> <span class="label">Full Name</span> <span class="field"> <input id="text-field-4" name="projects" placeholder="Jane Doe" required type="text"> </span> </label> <label class="text-field"> <span class="label">Email Address</span> <span class="field"> <input id="text-field-5" name="projects" placeholder="jane@example.com" required type="email"> </span> </label> <label class="select"> <span class="label" id="select-label-3">Role</span> <span class="field"> <select aria-labelledby="select-label-3" id="select-3" name="projects"> <button> <selectedcontent> </button> <div class="list"> <option value="dev">Developer</option><option value="design">Designer</option><option value="manager">Manager</option> </div> </select> </span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Notifications</legend><p class="field-description">Configure how you want to receive updates.</p> <div class="field-group" role="group"> <label class="switch"> <input type="checkbox" role="switch" checked name="email_notifs"><span class="label">Email Notifications</span> </label> <label class="switch"> <input type="checkbox" role="switch" name="sms_notifs"><span class="label">SMS Notifications</span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Theme Preference</legend><p class="field-description">Select your preferred visual style.</p> <div class="field-group" role="group"> <label class="radio"> <input type="radio" checked name="theme" value="light"><span class="label">Light Theme</span> </label> <label class="radio"> <input type="radio" name="theme" value="dark"><span class="label">Dark Theme</span> </label> <label class="radio"> <input type="radio" name="theme" value="system"><span class="label">System Default</span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Experience Level</legend><p class="field-description">How many years of experience do you have?</p> <div class="field-group" role="group"><script type="module">for(const n of document.querySelectorAll(".range")){const t=n.querySelector('input[type="range"]'),e=n.querySelector(":scope > output.value");if(!t||!e)continue;const o=e.dataset.suffix??"",u=()=>{e.textContent=`${t.value}${o}`};t.addEventListener("input",u),u()}</script> <label class="range"> <span class="label" id="range-label-6">Professional Experience</span> <span class="start-text" id="range-start-1"> Drag the slider to match your total tenure. </span> <input aria-describedby="range-start-1" aria-labelledby="range-label-6" id="range-6" max="20" min="0" step="1" type="range" value="5"> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Additional Info</legend><p class="field-description">Anything else we should know?</p> <div class="field-group" role="group"> <label class="textarea"> <span class="label">Biography</span> <span class="field"> <textarea id="textarea-2" name="details" placeholder="Tell us about yourself..." rows="4"></textarea> </span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Legal</legend> <div class="field-group" role="group"> <label class="checkbox"> <input type="checkbox" name="terms" required aria-describedby="end-text-6"><span class="label">I agree to the terms and conditions </span> <span id="end-text-6" class="end-text">Support this text</span> </label> </div> </fieldset> <hr class="divider"> <div class="field-group" role="group"><button type="submit" class="button filled">Send</button><button class="button">Cancel</button> </div> </form>Row
Everything all at once, but horizontally.
---import { Button, Checkbox, Divider, FieldDescription, FieldGroup, FieldLegend, FieldSet, Form, Radio, Range, Select, Switch, TextField, Textarea,} from "@opui/astro"---
<Form id="kitchen-sink-example-row"> <FieldSet> <FieldLegend>User Profile</FieldLegend> <FieldDescription> Please provide your basic contact details. </FieldDescription> <FieldGroup> <TextField placeholder="Jane Doe" required spread> <Fragment slot="label">Full Name</Fragment> </TextField> <TextField type="email" placeholder="jane@example.com" required spread> <Fragment slot="label">Email Address</Fragment> </TextField> <Select items={[ { text: "Developer", value: "dev" }, { text: "Designer", value: "design" }, { text: "Manager", value: "manager" }, ]} spread > <Fragment slot="label">Role</Fragment> </Select> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Notifications</FieldLegend> <FieldDescription> Configure how you want to receive updates. </FieldDescription> <FieldGroup name="notifications"> <Switch name="email_notifs" checked spread>Email Notifications</Switch> <Switch name="sms_notifs" spread>SMS Notifications</Switch> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Theme Preference</FieldLegend> <FieldDescription>Select your preferred visual style.</FieldDescription> <FieldGroup direction="row" name="theme"> <Radio value="light" checked>Light Theme</Radio> <Radio value="dark">Dark Theme</Radio> <Radio value="system">System Default</Radio> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Experience Level</FieldLegend> <FieldDescription >How many years of experience do you have?</FieldDescription > <FieldGroup> <Range min="0" max="20" step="1" value="5" spread> Professional Experience <Fragment slot="start-text" >Drag the slider to match your total tenure.</Fragment > </Range> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Additional Info</FieldLegend> <FieldDescription>Anything else we should know?</FieldDescription> <FieldGroup name="details"> <Textarea placeholder="Tell us about yourself..." rows={4} spread> <Fragment slot="label">Biography</Fragment> </Textarea> </FieldGroup> </FieldSet>
<Divider />
<FieldSet> <FieldLegend>Legal</FieldLegend> <FieldGroup name="legal"> <Checkbox name="terms" required spread> I agree to the terms and conditions <Fragment slot="end-text">Support this text</Fragment> </Checkbox> </FieldGroup> </FieldSet>
<Divider />
<FieldGroup> <Button variant="filled" type="submit">Send</Button> <Button>Cancel</Button> </FieldGroup></Form><form id="kitchen-sink-example-row" class="form"> <fieldset class="fieldset"><legend>User Profile</legend><p class="field-description">Please provide your basic contact details.</p> <div class="field-group" role="group"> <label class="text-field spread"> <span class="label">Full Name</span> <span class="field"> <input id="text-field-6" name="legal" placeholder="Jane Doe" required type="text"> </span> </label> <label class="text-field spread"> <span class="label">Email Address</span> <span class="field"> <input id="text-field-7" name="legal" placeholder="jane@example.com" required type="email"> </span> </label> <label class="select spread"> <span class="label" id="select-label-4">Role</span> <span class="field"> <select id="select-4" name="legal"> <button> <selectedcontent> </button> <div class="list"> <option value="dev">Developer</option><option value="design">Designer</option><option value="manager">Manager</option> </div> </select> </span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Notifications</legend><p class="field-description">Configure how you want to receive updates.</p> <div class="field-group" role="group"> <label class="switch spread"> <input type="checkbox" role="switch" checked name="email_notifs"><span class="label">Email Notifications</span> </label> <label class="switch spread"> <input type="checkbox" role="switch" name="sms_notifs"><span class="label">SMS Notifications</span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Theme Preference</legend><p class="field-description">Select your preferred visual style.</p> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" checked name="theme" value="light"><span class="label">Light Theme</span> </label> <label class="radio"> <input type="radio" name="theme" value="dark"><span class="label">Dark Theme</span> </label> <label class="radio"> <input type="radio" name="theme" value="system"><span class="label">System Default</span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Experience Level</legend><p class="field-description">How many years of experience do you have?</p> <div class="field-group" role="group"> <label class="range spread"> <span class="label" id="range-label-7">Professional Experience </span> <span class="start-text" id="range-start-2"> Drag the slider to match your total tenure. </span> <input aria-describedby="range-start-2" aria-labelledby="range-label-7" id="range-7" max="20" min="0" step="1" type="range" value="5"> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Additional Info</legend><p class="field-description">Anything else we should know?</p> <div class="field-group" role="group"> <label class="textarea spread"> <span class="label">Biography</span> <span class="field"> <textarea id="textarea-3" name="details" placeholder="Tell us about yourself..." rows="4"></textarea> </span> </label> </div> </fieldset> <hr class="divider"> <fieldset class="fieldset"><legend>Legal</legend> <div class="field-group" role="group"> <label class="checkbox spread"> <input type="checkbox" name="terms" required aria-describedby="end-text-7"><span class="label">I agree to the terms and conditions </span> <span id="end-text-7" class="end-text">Support this text</span> </label> </div> </fieldset> <hr class="divider"> <div class="field-group" role="group"><button type="submit" class="button filled">Send</button><button class="button">Cancel</button> </div> </form>API
Form
| Prop | Type | Default | Description |
|---|---|---|---|
as | string | "form" | The HTML element to render. |
FieldSet
| Prop | Type | Default | Description |
|---|---|---|---|
as | "fieldset", "div" | "fieldset" | The HTML element to render. |
disabled | boolean | - | Whether the field set is disabled. |
FieldLegend
| Prop | Type | Default | Description |
|---|---|---|---|
as | "legend", "p", "div", "span" | "legend" | The HTML element to render. |
FieldDescription
| Prop | Type | Default | Description |
|---|---|---|---|
| - | [key: string]: any | - | Field description for the field set. Accepts standard HTML attributes. |
FieldGroup
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | - | Shared name for nested input components. |
direction | "row", "column" | - | The orientation of the field group. |
Browser support
See also the full browser support guide.
Installation
This doesn't include all the styles for all form elements, just the scaffolding around them.
See also
@layer components.extended {
/* Form */ :where(.form) { display: grid; gap: var(--size-8);
hr { margin-block: 0; } }
/* Fieldset */ :where(.fieldset) { all: unset; border: 0; border-radius: 0; display: grid; gap: var(--size-1); margin: 0; min-inline-size: 0; padding: 0;
/* Legend */ :where(legend, .legend) { all: unset; color: var(--text-primary); font-weight: 600; margin-block-end: var(--size-3); padding: 0;
&:has(+ :where(.field-description)) { margin-block-end: 0; } }
/* Field description / supporting text */ :where(.field-description) { color: var(--text-muted); font-size: var(--font-size-05); line-height: var(--font-lineheight-3);
&:has(+ *) { margin-block-end: var(--size-3); } }
/* End text */ :where(.end-text) { color: var(--text-muted); font-size: var(--font-size-0); line-height: var(--font-lineheight-3); }
&:has(.text-field.row) { row-gap: var(--size-7); }
/* Disabled */ &[disabled] { opacity: 0.64; user-select: none;
input, label, .label { cursor: not-allowed; } }
/* Error / validation */ &[data-invalid] { :where(.end-text) { color: var(--primary); }
:where(.checkbox, .radio, .switch) { --palette-source: oklch(0.58 0.21 var(--hue-red));
:where(.end-text) { color: var(--primary); } }
:where(.switch) { input { border-radius: var(--radius-round); outline: 2px solid var(--primary); } } }
/* Required */ &:has(:invalid) { :where(legend, .legend) { padding-inline-end: 1ex; position: relative;
&::after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } } } }
/* Field group */ :where(.field-group) { display: flex; flex-direction: column; gap: var(--size-4);
/* If it only has checkboxes, radios, or switches */ &:has(> :where(.checkbox, .radio, .switch)):not(:has(> :not(.checkbox, .radio, .switch))) { gap: var(--size-2); }
/* If it only has buttons */ &:has(.button):not(:has(*:not(.button))) { align-items: center; flex-direction: row; gap: var(--size-2);
/* If it's not preceeded by an hr */ &:not(hr + &) { margin-block-start: var(--size-4); } }
&+& { margin-block-start: var(--size-5); }
/* Directions */ &.row { flex-direction: row; flex-wrap: wrap;
&:has(> :where(.checkbox, .radio, .switch)) { gap: var(--size-4); } } }}