Form Group, Fieldset and Label
Canonical native field composition, labels, helper text and grouping
Form Group, Fieldset and Label
Prefer one associated label, a native control and described helper/error text. Form Group styles composition, not values or validation logic.
Imports
Individual example prerequisites (including composition and demo actions):
@import "@duskmoon-dev/core/base.css";
@import "@duskmoon-dev/core/themes/sunshine";
@import "@duskmoon-dev/core/components/form.css";
@import "@duskmoon-dev/core/components/button.css";
base.css supplies global focus/utility definitions and shared spacing/shape tokens;
use your framework reset when appropriate. Substitute the Moonlight theme for dark forms.
Aggregate: @duskmoon-dev/core; no-Tailwind: @duskmoon-dev/core/standalone.css.
Individual: @duskmoon-dev/core/components/form-group.css plus theme/reset and the
desired control’s CSS. Legacy form.css includes this composition.
Extensionless components/form-group exports generated css and styles.
Labels
Associated label and help
Use your account email.
Error: enter a valid email address.
<div class="form-group">
<label class="form-label form-label-required" for="demo-field-email">Email (required)</label>
<input class="input validator" id="demo-field-email" name="email" type="email" required aria-describedby="demo-field-help demo-field-error">
<p class="helper-text" id="demo-field-help">Use your account email.</p>
<p class="validator-error" id="demo-field-error">Error: enter a valid email address.</p>
</div>Put required on the control. An asterisk, .form-label-required or
legacy label aria-required styling is not a substitute for native semantics.
Use unique IDs, especially in repeated fields. Nesting a control in its label is also valid.
.form-label is the preferred block field label. Legacy .label is a flexible
label-row layout; .label-text styles its caption and .label-text-alt its ancillary text.
Legacy label row
Displayed with your account.
<div class="form-control">
<label class="label" for="demo-legacy-name"><span class="label-text">Name</span><span class="label-text-alt">Optional</span></label>
<input class="input" id="demo-legacy-name" name="name" aria-describedby="demo-legacy-help">
<p class="helper-text" id="demo-legacy-help">Displayed with your account.</p>
</div>Fieldset
Use native fieldset and legend to give a meaningful group name, especially
for radio and checkbox choices. Fieldset does not add behavior to custom controls.
Native radio group
<form>
<fieldset class="fieldset">
<legend class="fieldset-legend">Delivery method</legend>
<label class="radio-label"><input class="radio" type="radio" name="delivery" value="email" checked>Email</label>
<label class="radio-label"><input class="radio" type="radio" name="delivery" value="sms">SMS</label>
</fieldset>
<button class="btn" type="reset">Reset to Email</button>
</form>Genuinely disabled fieldset
<fieldset class="fieldset fieldset-filled" disabled>
<legend class="fieldset-legend">Unavailable preferences</legend>
<label class="checkbox-label"><input type="checkbox" name="newsletter" class="checkbox" checked>Newsletter</label>
<label class="form-label" for="demo-fieldset-disabled-input">Account name</label>
<input id="demo-fieldset-disabled-input" name="account-name" class="input" value="Disabled">
</fieldset>Native fieldset disabled prevents editing, focus and submission of descendants,
except controls in the first legend as defined by HTML. Avoid putting editable controls in
that legend unless the exception is intended. Visual .form-group-disabled is retained
only for compatibility and does not prevent keyboard editing or submission.
Help, Errors And Layout
Associate persistent help and errors with aria-describedby. Explicit errors use
aria-invalid="true" and useful text, not color alone. See the
Validator contract for precedence and opt-in native states.
| Classes | Role |
|---|---|
.form-group, .form-control |
Vertical field composition; legacy control retains margin |
.form-group-horizontal |
Label/control row; stacks on narrow layouts |
.form-label-sm/lg, .form-label-optional |
Label presentation |
.helper-text, .helper-text-error/success/warning/info |
Helper presentation |
.helper-text.error/success/warning/info |
Retained legacy aliases |
.helper-text-icon, .form-hint |
Associated hint layouts |
.fieldset-filled/borderless/card |
Existing group presentation |
.form-row, .form-grid, .form-grid-2/3/4 |
Responsive grouped field layouts |
.form-inline, .form-section/title/description |
Form composition |
.form-actions, form-actions-right/center/between |
Action placement |
.form-divider, .form-divider-text |
Section separation |
.form-counter, .form-counter-error |
Application-updated counter presentation |
.form-group-error/success/warning/info, .form-control.error/success/warning/info |
Retained explicit wrapper presentation |
CSS does not count characters or disable native controls through a wrapper class. Counters and dynamic messages require application updates; native values/reset remain the browser’s responsibility.