Field
Field is a low-level wrapper for custom, native, or third-party controls that do not already provide field label, description, and status UI. Use it when you need the Field shell around a control you own; use styled Astryx inputs like TextInput, Typeahead, and Select directly when they already expose label, description, and validation props.
Installation
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
Requires React 19+, base CSS and a Theme provider. See Setup.
Field
Field is a low-level wrapper for custom, native, or third-party controls that do not already provide field label, description, and status UI. Use it when you need the Field shell around a control you own; use styled Astryx inputs like TextInput, Typeahead, and Select directly when they already expose label, description, and validation props.
| Prop | Type | Default | Description |
|---|---|---|---|
| label * | string | Label text for the field (always rendered for accessibility). | |
| inputID * | string | ID for the input element (used for the label htmlFor attribute). | |
| labelID | string | ID applied to the label element itself for group accessibility. | |
| isGroupLabel | boolean | false | Renders the label as a span for control groups (radiogroup, checkbox list). |
| children * | ReactNode | The input or control to render. | |
| isLabelHidden | boolean | false | Visually hide the label (still accessible to screen readers). |
| isDisabled | boolean | false | Whether the associated input is disabled. Propagates disabled styling to the label. |
| description | string | Description text displayed between the label and input. | |
| descriptionID | string | ID for the description element (use for aria-describedby on the input). | |
| isOptional | boolean | false | Whether the field is optional (mutually exclusive with isRequired). |
| isRequired | boolean | false | Whether the field is required (mutually exclusive with isOptional). |
| labelIcon | IconType | Icon to display before the label text. See astryx docs icons for valid semantic names. | |
| labelTooltip | string | Tooltip text to display in an info icon at the end of the label. | |
| status | {type: 'warning' | 'error' | 'success', message?: string, messageID?: string} | Status indicator with type and optional message. When message is set, displays a colored status box. messageID is for wiring aria-describedby on the input. | |
| statusVariant | 'attached' | 'detached' | 'attached' | How the status message renders relative to the input. Attached overlaps the input border; detached floats below. |
| width | SizeValue | Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned. Prefer this over setting width via xstyle/className/style, which only size the inner control box. Without a width, a row can shrink the whole field below the natural width of its control, so filter bars shrink instead of overflowing. | |
| ref | React.Ref<HTMLDivElement> | Ref forwarded to the root element. | |
| xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value: not an inline style object like style={{}}. | |
| className | string | CSS class name(s) appended to the root element. Prefer xstyle for StyleX deduplication. | |
| style | React.CSSProperties | Inline styles applied to the root element. Takes priority over StyleX inline styles. |
Usage guidance
- Wrap custom controls, native inputs, or third-party widgets that need labeling, helper text, optional/required indicators, or validation status.
- Always provide a label for accessibility, even if visually hidden with isLabelHidden.
- Use inputID and descriptionID to connect the label and description to the inner control with htmlFor and aria-describedby.
- Avoid: Nest Field around styled inputs such as TextInput, Typeahead, Select, DateInput, or TextArea; those components already render their own Field shell.
- Avoid: Use the attached status variant on non-bordered controls such as sliders, switches, or checkboxes; use detached so the message does not overlap the control.
- Avoid: Set both isOptional and isRequired on the same field.
- Avoid: Hide the label without providing an alternative way for the user to understand the field purpose.
Filter bar
import {useState} from 'react';
import {HStack} from '@astryxdesign/core/HStack';
import {Icon} from '@astryxdesign/core/Icon';
import {Selector} from '@astryxdesign/core/Selector';
import {StackItem} from '@astryxdesign/core/Stack';
import {TextInput} from '@astryxdesign/core/TextInput';
// Every Field-based input (TextInput, Selector, DateInput, ...) can be shrunk
// by its row, so a filter bar never pushes the page sideways. Let the row wrap
// so that on a phone the search box keeps a full line and the selectors move
// below it; on wider screens everything stays on one line, with the search box
// taking the free space.
function OrdersFilterBar() {
const [query, setQuery] = useState('');
const [status, setStatus] = useState('Awaiting fulfillment');
const [region, setRegion] = useState('All regions');
const [sort, setSort] = useState('Newest first');
return (
<HStack gap={2} wrap="wrap" vAlign="center">
<StackItem size="fill">
<TextInput
label="Search orders"
isLabelHidden
placeholder="Search by order or customer"
startIcon={<Icon icon="search" />}
value={query}
onChange={setQuery}
/>
</StackItem>
<Selector
label="Status"
isLabelHidden
options={['All statuses', 'Awaiting fulfillment', 'Shipped', 'Delivered', 'Returned']}
value={status}
onChange={setStatus}
/>
<Selector
label="Region"
isLabelHidden
options={['All regions', 'North America', 'Europe', 'Asia Pacific']}
value={region}
onChange={setRegion}
/>
<Selector
label="Sort"
isLabelHidden
options={['Newest first', 'Oldest first', 'Highest total']}
value={sort}
onChange={setSort}
/>
</HStack>
);
}
Wrap a custom control
function CustomSliderField() {
return (
<Field
label="Confidence"
inputID="confidence-slider"
description="Choose how strict the review should be."
descriptionID="confidence-help"
status={{type: 'success', message: 'Recommended default'}}
statusVariant="detached">
<input
id="confidence-slider"
type="range"
min={0}
max={100}
defaultValue={60}
aria-describedby="confidence-help"
/>
</Field>
);
}
Theming API
{
"targets": [
{
"className": "astryx-field",
"visualProps": [
"layout"
]
},
{
"className": "astryx-field-label"
},
{
"className": "astryx-field-status",
"visualProps": [
"type",
"variant"
]
},
{
"className": "astryx-input-status-icon",
"visualProps": [
"size",
"status"
]
},
{
"className": "astryx-input-clear-button"
},
{
"className": "astryx-input-clear-icon"
}
],
"vars": [
{
"name": "--_field-radius",
"description": "Border radius of input fields",
"default": "var(--radius-element)",
"private": true
},
{
"name": "--_field-status-overlap",
"description": "Amount an attached FieldStatus extends behind the lower half of the control. Set from the rendered control size.",
"default": "calc(var(--size-element-md) / 2)",
"private": true
},
{
"name": "--_input-clear-hit-inset",
"description": "Outset of the clear (✕) button's invisible hit area, applied to a ::after overlay. 0 on a fine pointer; negative on a coarse one, which grows the 20px button to the 24px touch target without changing what is drawn.",
"default": "0px",
"private": true
},
{
"name": "--_input-clear-hit-content",
"description": "Whether the clear (✕) button's invisible hit overlay exists. `none` on a fine pointer, so no ::after is generated and hover still reaches the glyph; `\"\"` on a coarse one, where the overlay provides the 24px touch target.",
"default": "none",
"private": true
}
],
"derived": [
{
"property": "borderRadius",
"vars": [
"--_field-radius"
]
}
]
}
FieldLabel
FieldStatus
Anatomy
- Label: Text identifying the field. Always rendered for accessibility, optionally hidden visually.
- Description: Helper text between the label and input explaining what to enter.
- Control slot: A custom, native, or third-party control that does not already render a field shell.
- Status message: Inline validation feedback showing error, warning, or success with a message.
- Optional/Required indicator: Badge next to the label showing whether the field is optional or required.
- Label tooltip: Info icon at the end of the label with a tooltip explaining the field.
Field Label
Standalone label component with optional/required indicators and tooltip support.
| Prop | Type | Default | Description |
|---|---|---|---|
| label * | string | Label text. | |
| inputID * | string | ID of the input this label is for. | |
| isLabelHidden | boolean | false | Visually hide the label. |
| isDisabled | boolean | false | Whether the associated input is disabled. |
| isOptional | boolean | false | Show "Optional" indicator. |
| isRequired | boolean | false | Show "Required" indicator. |
| labelIcon | IconType | Icon before the label text. See astryx docs icons for valid semantic names. | |
| labelTooltip | string | Tooltip text for info icon at end of label. |
Examples
- Field Label: Standalone field labels demonstrating required, optional, tooltip, and icon variations.
- Field: A form field wrapping a text input with a label, description, and validation status.
- FieldLabel — Basic: Standalone labels with required and optional indicators and a helper description. Use when labeling a custom control that does not render its own label.
- Field — Required & Optional: Required and optional field indicators side by side. Use isRequired on fields the user must fill in, and isOptional to clarify which fields can be skipped.
- Field — Validation States: All three validation states: error, warning, and success. Use error for invalid input, warning for potential issues like reserved names, and success to confirm valid entries like API keys.
- Field — Description: Fields with helper text below the label. Use descriptions to explain format requirements, constraints, or what happens with the data, like "At least 8 characters" or "We will send a confirmation link".