Form guidelines
Native form controls in the light DOM with duo-field — markup order, helpers, errors, and accessibility.
Form guidelines
Forms should use predictable structure, native controls in the light DOM, and accessibility preserved end-to-end.
See also the Field component reference.
Primary field primitive: <duo-field>
<duo-field> wraps native input, textarea, select, checkbox, and radio elements. It does not replace those controls or expose a parallel input API.
- Do not add Duo field CSS classes in product markup —
duo-fieldapplies internalduo-field__*classes to direct children only at runtime. - Slotted custom controls (
duo-file-input,duo-otp-input, …) receive the host’sinvalid/disabledfromduo-fieldautomatically.
Supported markup (direct children only)
Children must be direct descendants of <duo-field>. Order is part of the contract.
Text-like field (input, textarea, select)
Order: label → control → optional p / small (helper or error).
<duo-field> <label for="email">Email</label> <input id="email" type="email" name="email" autocomplete="email" /> <p>We'll send a code to your email.</p></duo-field>Invalid state:
<duo-field invalid> <label for="email">Email</label> <input id="email" type="email" aria-describedby="email-error" aria-invalid="true" /> <p id="email-error" role="alert">Enter a valid email address.</p></duo-field>Use a plain <p> or <small> for helper text — not custom CSS classes on hints.
Checkbox
Order: input[type=checkbox] → label → optional helper/error.
<duo-field> <input id="remember" type="checkbox" name="remember" /> <label for="remember">Remember me on this device</label></duo-field>Radio
One radio + label per row when using separate fields:
<duo-field> <input id="contact-email" type="radio" name="contact-method" value="email" /> <label for="contact-email">Email</label></duo-field>Unsupported
Checkbox or radio nested inside label is not supported.
<!-- Not supported --><duo-field> <label> <input type="checkbox" /> <span>Remember me</span> </label></duo-field>Use the sibling pattern above instead.
Helper and error text
Optional p / small nodes may be styled using:
role="alert", or id containingerror→ error styling- id containing
hint→ hint styling - id containing
description/desc→ description styling - Otherwise: before the first control → description; after → hint
Set aria-describedby / aria-invalid on the native control when needed.
Host attributes (invalid, disabled, required)
invalid— Invalid styling on the host and control chrome. Does not replacearia-invalidlogic.disabled— Disables direct-child controls; clearing the host restores prior disabled state.required— Marks the primary text-like control required; clearing restores prior required state.
CSS import order
Load tokens → @duo/styles → @duo/ui so variables and field chrome match.
import "@duo/tokens/css";import "@duo/styles";import "@duo/ui";When to add another component
Keep duo-field for simple fields. Add dedicated components for complex widgets (combobox, date picker, file upload, …) only when justified.