@xsolla/xui-field-group
v0.209.1
Published
A cross-platform wrapper that adds a label, required indicator, optional icon, description, and helper text around a form field with consistent vertical spacing.
Readme
FieldGroup
A cross-platform wrapper that adds a label, required indicator, optional icon, description, and helper text around a form field with consistent vertical spacing.
A layout wrapper that adds a label, an optional description, and helper text to any form control. FieldGroup is not an input itself — it is the scaffolding that gives a field its visual and semantic identity within a form. Any input component from the design system can be placed in the content slot.
When to use
- Whenever a form control needs a visible label, description, or helper text
- To ensure consistent vertical spacing, label alignment, and text hierarchy across all fields in a form
- To apply validation messages and error states in a standardised way
- To wrap non-standard controls (ToggleButtonGroup, ImageUploader, InputRange, Drag&Drop Uploader) in the same form field visual pattern as regular inputs
When not to use
- When a field needs no label and no helper text — place the control directly without a FieldGroup wrapper
- As a layout grid for multi-column form rows — use a separate grid or flex layout to position FieldGroup instances side by side
- To group multiple related fields under one heading — use a fieldset / legend pattern or a form section component instead
Content guidelines
- Label — short, specific, sentence case. Aim for 1–3 words. Examples: "Email", "Card number", "End date", "Company name".
- Description — one line, informational, no punctuation at end. Example: "Visible to all team members", "YYYY-MM-DD format".
- Helper text (hint) — factual, concise. One line. Example: "8–20 characters", "CSV files only", "Enter the 6-digit code from your authenticator app".
- Helper text (error) — specific and actionable. Say what went wrong and what to do: "Enter a valid email address" not "Invalid value". "File must be smaller than 10 MB" not "File too large".
- Avoid redundancy — if the label clearly communicates the field's purpose, do not repeat it in the description or helper text.
Behaviour guidelines
- Label association — the label must be programmatically linked to the control. For standard inputs use <label for="fieldId">. For complex controls (ToggleButtonGroup, InputRange, ImageUploader) that are not native elements, use aria-labelledby on the control's container or role="group" with aria-label.
- Required fields — mark required fields visually (asterisk) and in code (aria-required="true" on the control or required on the input). Always explain the required field convention at the top of the form.
- Error state — when a field fails validation, the embedded control switches to its State=Error appearance, and the helper text changes to the error message in error colour. Both transitions happen simultaneously. Clear the error when the user begins correcting the value.
- Description visibility — Description is hidden by default. Enable it only for fields that genuinely need extra context. Avoid enabling Description on every field — it adds visual weight and reduces form scannability.
- Size consistency — all FieldGroup instances in the same form section must share the same Size. Mixing sizes within a form breaks the visual rhythm and makes labels and helper text appear inconsistent.
- Read-only fields — for fields that display a value but are not editable, use Label=true and the appropriate read-only control presentation. Do not use State=Disable for read-only display — reserve Disable for temporarily unavailable fields.
Accessibility
- The Label must be associated with the control via (for native inputs) or aria-labelledby / aria-label (for custom controls). Never use Description or Helper text as the only accessible name.
- For complex controls in the content slot (ToggleButtonGroup, InputRange, ImageUploader, Drag&Drop Uploader), wrap them in a role="group" with aria-labelledby pointing to the FieldGroup's label element.
- When Helper text shows an error, wrap it in an aria-live="polite" region so screen readers announce the error when it appears without requiring navigation. Also link it via aria-describedby on the control so it is read when the control receives focus.
- Mark required fields with aria-required="true" on the embedded control, in addition to the visual asterisk on the label.
- When Description=true, associate the description text with the control via aria-describedby alongside the helper text. Both are supplementary — only the label is mandatory for an accessible name.
- Ensure colour is not the only way to distinguish the error state — the error message in helper text provides the text-based signal required by WCAG 1.4.1 (Use of Colour).
Installation
npm install @xsolla/xui-field-groupImports
import { FieldGroup } from "@xsolla/xui-field-group";Quick start
import * as React from "react";
import { FieldGroup } from "@xsolla/xui-field-group";
import { Input } from "@xsolla/xui-input";
export default function QuickStart() {
return (
<FieldGroup label="Username">
<Input placeholder="Enter username" />
</FieldGroup>
);
}API Reference
<FieldGroup>
Extends BoxProps (minus children); any layout/style prop the underlying Box accepts is forwarded.
| Prop | Type | Default | Description |
| -------------- | -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| children | ReactNode | - | Form field component. |
| label | ReactNode | - | Label content. |
| required | boolean | — | Show a leading red asterisk. |
| description | ReactNode | - | Descriptive text rendered below the label and above the field. |
| helper | ReactNode | - | Helper text rendered below the field. |
| icon | ReactElement | - | Icon rendered after the label. Sized to match the chosen size. |
| size | "xl" \| "lg" \| "md" \| "sm" \| "xs" | "md" | Sizing token; resolves font size, line height, icon size, and gap via theme.sizing.input. |
| htmlFor | string | - | Associates the label with a form control by id. |
| marginBottom | number \| string | - | Bottom margin for vertical rhythm. |
| data-testid | string | - | Test identifier. |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
If a child renders its own errorMessage, the helper margin is collapsed automatically to avoid double spacing.
Anatomy
import { FieldGroup } from "@xsolla/xui-field-group";
<FieldGroup
label="Field label"
required
description="Description text"
helper="Helper text"
size="md"
htmlFor="field-id"
>
<FormControl id="field-id" />
</FieldGroup>;Examples
Required field with helper
import * as React from "react";
import { FieldGroup } from "@xsolla/xui-field-group";
import { Input } from "@xsolla/xui-input";
export default function RequiredHelper() {
return (
<FieldGroup label="Email" required helper="We'll never share your email">
<Input type="email" placeholder="[email protected]" />
</FieldGroup>
);
}Label icon
import * as React from "react";
import { FieldGroup } from "@xsolla/xui-field-group";
import { Input } from "@xsolla/xui-input";
import { Info } from "@xsolla/xui-icons-base";
export default function LabelIcon() {
return (
<FieldGroup label="API key" icon={<Info />}>
<Input placeholder="Enter API key" />
</FieldGroup>
);
}Sizes
import * as React from "react";
import { FieldGroup } from "@xsolla/xui-field-group";
import { Input } from "@xsolla/xui-input";
export default function Sizes() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<FieldGroup size="xs" label="Extra small" helper="Helper">
<Input size="xs" placeholder="xs" />
</FieldGroup>
<FieldGroup size="sm" label="Small" helper="Helper">
<Input size="sm" placeholder="sm" />
</FieldGroup>
<FieldGroup size="md" label="Medium" helper="Helper">
<Input size="md" placeholder="md" />
</FieldGroup>
<FieldGroup size="lg" label="Large" helper="Helper">
<Input size="lg" placeholder="lg" />
</FieldGroup>
<FieldGroup size="xl" label="Extra large" helper="Helper">
<Input size="xl" placeholder="xl" />
</FieldGroup>
</div>
);
}Form layout
import * as React from "react";
import { FieldGroup } from "@xsolla/xui-field-group";
import { Input } from "@xsolla/xui-input";
import { Button } from "@xsolla/xui-button";
export default function FormLayout() {
return (
<div style={{ maxWidth: 400 }}>
<FieldGroup label="Full name" required marginBottom={16}>
<Input placeholder="Jane Doe" />
</FieldGroup>
<FieldGroup
label="Email"
required
helper="We'll never share your email"
marginBottom={16}
>
<Input type="email" placeholder="[email protected]" />
</FieldGroup>
<FieldGroup marginBottom={0}>
<Button>Submit</Button>
</FieldGroup>
</div>
);
}Accessibility
- Pass
htmlFormatching the wrapped control'sidso the label associates with the field. - Helper text reads as part of the field via the surrounding form control's
aria-describedbywhen applicable. - The required asterisk is visual only; communicate required state via the form control's
required/aria-required.
