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')
);
Playground

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')
);
Playground

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')
);
Playground

Markup

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

VariableDefaultNotes
$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-size0.85emHelp text scale. em-relative so it tracks the surrounding font-size.
$help-mix70%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

app.scss
@use '@adnap/krysalicss/module/field' with (
  $gap: 0.375rem,
  $help-font-size: 0.8em,
  $invalid-icon: '⚠ ',
);

Tokens consumed

TokenUsed for
--kc-danger-bgHelp text colour under .is-invalid, and the invalid-glyph colour on the label.
--kc-state-icon-dangerGlyph 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-invalid avoids the false-positive red ring on untouched required fields; [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.