Skip to main content

rc-field

Accessible field wrapper for a native input or textarea with labels and supporting text.

Package
@rcarls/rc-field
Element
<rc-field>
Native dependency
Requires one direct child native input or textarea
State model
Derived from the native control; invalid can also be set explicitly
Main events
Native input, change, and invalid events

Installation​

npm install @rcarls/rc-webcomponents@next
import '@rcarls/rc-webcomponents/rc-field/define';

Usage​

Place exactly one native <input> or <textarea> directly inside the field. The control stays in light DOM and remains the source of truth for its value, form submission, autofill, browser validation, keyboard behavior, and native events.

<rc-field counter>
<label slot="label" for="recipe-title">Recipe title</label>
<input id="recipe-title" name="title" maxlength="80" placeholder="Tomato soup" required />
<small slot="hint">Use the title printed on the recipe.</small>
<small slot="error">Enter a recipe title.</small>
</rc-field>

Use matching for and id values when label semantics must work before the custom element is defined. They are preserved after upgrade. As a convenience, a slotted native label with no for attribute is connected to the control automatically after upgrade. Wrapping the complete field in a native label is also valid:

<label>
<rc-field>
<span slot="label">Notes</span>
<textarea name="notes"></textarea>
</rc-field>
</label>

Live demo​

Filled is the Material theme's default treatment. Add rc-field--outlined for the outlined variant. The unthemed component retains a compact, system-color presentation.

Grouped controls​

A <fieldset> placed directly inside the field groups several native controls into one field, such as an amount and its unit. The <legend> names the group and shows as the field label, and each control keeps its own accessible name, so a screen reader announces the group and then each control. Supporting text describes the fieldset.

<rc-field>
<fieldset>
<legend>Serving size</legend>
<input type="number" name="amount" aria-label="Amount" />
<select name="unit" aria-label="Unit">
<option value="g">g</option>
<option value="cup">cups</option>
</select>
</fieldset>
<small slot="hint">Optional</small>
</rc-field>

The first text-like input or textarea is the primary control: control returns it, focus() moves to it, and data-populated follows its value. The field stays focused while focus moves between its controls. data-disabled follows the fieldset's disabled, data-required any required control, and data-invalid any control that fails validation, which also gets aria-invalid. Use controls for every grouped control and group for the fieldset.

The primary control grows to fill the row. Selects and enhanced controls such as rc-select keep their own width and attach before or after it in source order.

Validation and supporting text​

Native constraint validation remains authoritative. A required field stays visually quiet until the control dispatches invalid, such as during form submission or reportValidity(). At that point the error slot replaces the hint and the native control receives aria-invalid="true" and the error's description. Author-provided aria-describedby and aria-invalid values are preserved.

Use setCustomValidity() for application validation so the native control, form, and field agree:

const field = document.querySelector('rc-field');
const input = field.control;

input.setCustomValidity(isAvailable(input.value) ? '' : 'That name is already in use.');
input.reportValidity();

The invalid property is available when an application owns a separate validation lifecycle. It sets the visual state and supplies aria-invalid when the author has not supplied it, but does not change ValidityState or prevent form submission.

Affixes and actions​

Use leading and trailing for icons or controls outside the editable content. Use prefix and suffix for content adjacent to the value. Interactive trailing content, such as a password visibility button, keeps its own focus and click behavior.

<rc-field class="rc-field--outlined">
<span slot="leading" aria-hidden="true">$</span>
<label slot="label" for="budget">Budget</label>
<input id="budget" name="budget" type="number" inputmode="decimal" />
<span slot="suffix">USD</span>
<button slot="trailing" type="button" aria-label="Clear budget">×</button>
</rc-field>

Prefer native attributes such as type, inputmode, autocomplete, pattern, min, and max for formatting and validation affordances. rc-field intentionally does not rewrite values or implement input masking.

Programmatic updates​

Native input and change events, constraint-attribute changes, form reset, and replacement of the direct control synchronize automatically. Assigning input.value does not emit an event, so call sync() afterward:

field.control.value = 'Tomato soup';
field.sync();

focus() and blur() delegate to the native control. The read-only control property returns the current direct input or textarea.

Accessibility​

  • Always give the native control an accessible name with a native label, aria-label, or aria-labelledby. Prefer explicit for and id values for sibling labels so the relationship exists before upgrade.
  • Keep error text specific and actionable; do not rely on color alone.
  • Mark decorative icons aria-hidden="true". Give interactive leading or trailing controls their own accessible names.
  • The character counter is supplementary visual text. Put hard limits on the native control with maxlength so browsers and assistive technology receive the actual constraint.

API​

Properties​

PropertyMarkupTypeDefaultDescription
countercounterbooleanfalseShows the current native value length and `maxlength`, when present.
invalidinvalidbooleanfalseForces invalid presentation and supplies native `aria-invalid` when absent.
controlJS property onlyRCFieldControl | nullNot specifiedReturns the native control, or `null` when the field is incomplete. In a group, this is the primary control: the first text-like input or textarea, or else the first control.
groupJS property onlyHTMLFieldSetElement | nullNot specifiedReturns the `<fieldset>` that groups the field's controls, or `null` for a single control.
controlsJS property onlyRCFieldControl[]Not specifiedReturns every native control the field manages, in source order.

Methods​

MethodDescription
focus(options?: FocusOptions)Focuses the native control.
blur()Removes focus from the native control.
sync()Re-reads native value, validity, attributes, slots, and accessible relationships.

Slots​

NameDescription
(default)Required native `<input>`, `<textarea>`, or `<select>` control. An enhancing direct child marked `data-rc-field-control` may own the native control as its direct child. A `<fieldset>` with a `<legend>` instead groups several controls into one field.
labelVisible label; use a native `<label>` unless an ancestor label wraps the field
leadingLeading icon or control outside the editable content
trailingTrailing icon or control outside the editable content
prefixText or content immediately before the native control
suffixText or content immediately after the native control
hintSupporting guidance associated with the native control
errorError text shown and associated after validation is attempted

CSS Custom Properties​

PropertyDefaultDescription
--rc-field-min-block-sizevar(--rc-control-block-size, 2.5rem)Minimum field surface block size
--rc-field-gapvar(--rc-control-gap, 0.5rem)Gap between leading, content, and trailing regions
--rc-field-paddingvar(--rc-control-padding-block, 0.5rem) var(--rc-control-padding-inline, 0.75rem)Field surface padding
--rc-field-bordervar(--rc-border, 1px solid ButtonBorder)Field surface border
--rc-field-radiusvar(--rc-control-radius, 0.125rem)Field surface corner radius
--rc-field-backgroundvar(--rc-field, Field)Field surface background
--rc-field-colorvar(--rc-field-text, FieldText)Input and affix text color
--rc-field-label-colorvar(--rc-field-text, CanvasText)Label color
--rc-field-control-gapvar(--rc-control-gap, 0.25rem)Gap between control and prefix or suffix
--rc-field-supporting-gapvar(--rc-control-gap, 0.5rem)Gap between supporting text and counter
--rc-field-supporting-padding-block-start0.25remSupporting text top padding
--rc-field-supporting-padding-inlinevar(--rc-control-padding-inline, 0.75rem)Supporting text inline padding
--rc-field-supporting-colorvar(--rc-field-text, CanvasText)Hint and counter color
--rc-field-supporting-font-size0.75remHint, error, and counter font size
--rc-field-error-colorMarkInvalid outline, label, and error color
--rc-field-disabled-opacityvar(--rc-disabled-opacity, 0.6)Disabled field opacity
--rc-field-focus-outlinevar(--rc-focus-ring, 2px solid Highlight)Focus outline
--rc-field-focus-outline-offsetvar(--rc-focus-ring-offset, 0)Focus outline offset

CSS Parts​

PartDescription
fieldField surface
contentLabel and editable content column
labelLabel container
controlNative control, prefix, and suffix row
leadingLeading content container
trailingTrailing content container
prefixPrefix content container
suffixSuffix content container
supporting-textSupporting text row
hintHint container
errorError container
counterCharacter counter