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- Related
- Theme previews
Installation
- npm
- Yarn
npm install @rcarls/rc-webcomponents
yarn add @rcarls/rc-webcomponents
import '@rcarls/rc-webcomponents/rc-field/define';
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.
Native-first composition
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>
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, oraria-labelledby. Prefer explicitforandidvalues 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
maxlengthso browsers and assistive technology receive the actual constraint.
API
Properties
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
counter | counter | boolean | false | Shows the current native value length and `maxlength`, when present. |
invalid | invalid | boolean | false | Forces invalid presentation and supplies native `aria-invalid` when absent. |
control | JS property only | RCFieldControl | null | Not specified | Returns 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. |
group | JS property only | HTMLFieldSetElement | null | Not specified | Returns the `<fieldset>` that groups the field's controls, or `null` for a single control. |
controls | JS property only | RCFieldControl[] | Not specified | Returns every native control the field manages, in source order. |
Methods
| Method | Description |
|---|---|
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. |
Events
No custom events are documented in the custom elements manifest.
Slots
| Name | Description |
|---|---|
(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. |
label | Visible label; use a native `<label>` unless an ancestor label wraps the field |
leading | Leading icon or control outside the editable content |
trailing | Trailing icon or control outside the editable content |
prefix | Text or content immediately before the native control |
suffix | Text or content immediately after the native control |
hint | Supporting guidance associated with the native control |
error | Error text shown and associated after validation is attempted |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--rc-field-min-block-size | var(--rc-control-block-size, 2.5rem) | Minimum field surface block size |
--rc-field-gap | var(--rc-control-gap, 0.5rem) | Gap between leading, content, and trailing regions |
--rc-field-padding | var(--rc-control-padding-block, 0.5rem) var(--rc-control-padding-inline, 0.75rem) | Field surface padding |
--rc-field-border | var(--rc-border, 1px solid ButtonBorder) | Field surface border |
--rc-field-radius | var(--rc-control-radius, 0.125rem) | Field surface corner radius |
--rc-field-background | var(--rc-field, Field) | Field surface background |
--rc-field-color | var(--rc-field-text, FieldText) | Input and affix text color |
--rc-field-label-color | var(--rc-field-text, CanvasText) | Label color |
--rc-field-control-gap | var(--rc-control-gap, 0.25rem) | Gap between control and prefix or suffix |
--rc-field-supporting-gap | var(--rc-control-gap, 0.5rem) | Gap between supporting text and counter |
--rc-field-supporting-padding-block-start | 0.25rem | Supporting text top padding |
--rc-field-supporting-padding-inline | var(--rc-control-padding-inline, 0.75rem) | Supporting text inline padding |
--rc-field-supporting-color | var(--rc-field-text, CanvasText) | Hint and counter color |
--rc-field-supporting-font-size | 0.75rem | Hint, error, and counter font size |
--rc-field-error-color | Mark | Invalid outline, label, and error color |
--rc-field-disabled-opacity | var(--rc-disabled-opacity, 0.6) | Disabled field opacity |
--rc-field-focus-outline | var(--rc-focus-ring, 2px solid Highlight) | Focus outline |
--rc-field-focus-outline-offset | var(--rc-focus-ring-offset, 0) | Focus outline offset |
CSS Parts
| Part | Description |
|---|---|
field | Field surface |
content | Label and editable content column |
label | Label container |
control | Native control, prefix, and suffix row |
leading | Leading content container |
trailing | Trailing content container |
prefix | Prefix content container |
suffix | Suffix content container |
supporting-text | Supporting text row |
hint | Hint container |
error | Error container |
counter | Character counter |