Field
Wrapper that stacks a label, a single control, and an optional help paragraph in a vertical flex column. Adds a non-colour glyph cue to the label when the field contains an invalid control (WCAG 1.4.1) and a coordinated dimming for disabled fields. Pairs with every form control in element/forms.
Live
Label + control + help
We'll never share your email.
<div class="field">
<label class="field__label" for="demo-field-email">Email address</label>
<input id="demo-field-email" type="email" placeholder="name@example.com" />
<p class="field__help">We'll never share your email.</p>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'module-field', 'element-text-input')
); Invalid: glyph + tinted border + tinted help
Enter a valid email address.
<div class="field is-invalid">
<label class="field__label" for="demo-field-bad">Email</label>
<input id="demo-field-bad" type="email" value="not-an-email" aria-invalid="true" />
<p class="field__help">Enter a valid email address.</p>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'module-field', 'element-text-input')
); Disabled: dim label + help
This field can't be edited right now.
<div class="field is-disabled">
<label class="field__label" for="demo-field-disabled">Email</label>
<input id="demo-field-disabled" type="email" value="name@example.com" disabled />
<p class="field__help">This field can't be edited right now.</p>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'module-field', 'element-text-input')
); Markup
<div class="field">
<label class="field__label" for="email">Email</label>
<input id="email" type="email" placeholder="name@example.com" />
<p class="field__help">We'll never share it.</p>
</div>
<div class="field is-invalid">
<label class="field__label" for="email-bad">Email (invalid)</label>
<input id="email-bad" type="email" value="not-an-email" aria-invalid="true" />
<p class="field__help">Enter a valid email address.</p>
</div> When to use
- Around any single form control that needs a label and / or help text.
- Don't wrap multiple controls in one field — group them in a
<fieldset>instead and nest a field per control. - For purely inline controls (a bare toggle in a row), the wrapper is optional; skip it when the surrounding context already provides the label.
Invalid state: non-colour cue
The danger-tinted invalid border on each form control is paired with a
glyph prepended to the field's label, so the state isn't conveyed by
colour alone (WCAG 1.4.1). The glyph reads from
--kc-state-icon-danger and falls back to the SCSS
$invalid-icon default ("✕ "); set the custom
property to '' to drop it.
The cue fires automatically when the field contains a control matching
:user-invalid (post-interaction) or
[aria-invalid="true"], and also when the wrapper carries
the explicit .is-invalid modifier.
:user-invalid (rather than :invalid) means an
untouched required field will not be flagged on first
paint: the user must have blurred or attempted submission first.
Variables
| Variable | Default | Notes |
|---|---|---|
$selector | '.field' | Root wrapper. |
$label-selector | '.field__label' | Direct-child label. Painted bold by default. |
$help-selector | '.field__help' | Direct-child help text under the control. |
$invalid-selector | '.is-invalid' | Class hook on the wrapper. Synonym for the auto-detected :has(:user-invalid) + :has([aria-invalid="true"]) selectors. |
$disabled-selector | '.is-disabled' | Class hook on the wrapper. Dims label and help to mirror the control's own :disabled rule. |
$gap | $size-extra-small (0.5rem) | Vertical gap between label / control / help. |
$help-font-size | 0.85em | Help text scale. em-relative so it tracks the surrounding font-size. |
$help-mix | 70% | Help text colour: color-mix(in srgb, currentColor 70%, transparent). De-emphasised vs. body copy without falling below contrast. |
$margin-bottom | $global-gap (1rem) | Block-end margin between adjacent fields. |
$invalid-icon | '✕ ' | Glyph prepended to the label on invalid state. Default for the --kc-state-icon-danger token. Trailing space is U+0020 to separate the icon from the label text. |
Override example
@use '@adnap/krysalicss/module/field' with (
$gap: 0.375rem,
$help-font-size: 0.8em,
$invalid-icon: '⚠ ',
); Tokens consumed
| Token | Used for |
|---|---|
--kc-danger-bg | Help text colour under .is-invalid, and the invalid-glyph colour on the label. |
--kc-state-icon-danger | Glyph prepended to the label on invalid state. Owned by helper/state; override globally to swap every state icon, or per instance via inline custom property. |
Accessibility
- Pure layout wrapper; semantics come from the native control inside. Always reference the control with
<label for="…">— implicit-label nesting is brittle for assistive tech. - The invalid glyph is a non-colour cue paired with the danger-tinted border on the control, satisfying WCAG 1.4.1 (Use of Colour).
:user-invalidavoids the false-positive red ring on untouchedrequiredfields;[aria-invalid="true"]remains the manual hook for JS-validated forms.- Disabled-field dimming uses opacity 0.6 on label and help — clears WCAG 1.4.3 (4.5:1) against the page bg on shipped themes.
- Forced-colors mode inherits the page foreground because the field is a flex layout with no painted surface.
See also
- Forms overview — full form-control roster.
- Text input — single-line text-flavoured input; pairs with this wrapper.
- Select — dropdown one-of-many; pairs with this wrapper.
- Textarea — multi-line text input; pairs with this wrapper.
- Checkbox / Radio / Switch — same wrapper, indicator-style controls.