Skip to content

Field ​

A labeled control with an optional hint and error under it, stacked 6 pixels apart. FieldHint and FormError are the same two lines on their own, for a control that is not inside a Field.

tsx
import { Field, FieldHint, FormError } from '@basmilius/desktop-ui';
tsx
import { useState } from 'react';
import { Field, FieldHint, FormError, Input, Segmented } from '@basmilius/desktop-ui';

export default function FieldDemo() {
    const [name, setName] = useState('feature/export');
    const taken = name.trim() === 'main';
    const [ground, setGround] = useState('dark');

    return (
        <div className="flex w-80 flex-col gap-4">
            <Field label="Branch name" hint="Lowercase, with slashes for a folder." error={taken ? 'A branch called main already exists.' : null}>
                <Input mono value={name} onChange={(event) => setName(event.target.value)} />
            </Field>
            <Field group label="Ground" hint="What every frame is drawn on.">
                <Segmented
                    label="Ground"
                    value={ground}
                    onValueChange={setGround}
                    options={[
                        { id: 'dark', label: 'Dark' },
                        { id: 'light', label: 'Light' }
                    ]}
                />
            </Field>
            <div>
                <Input aria-label="Remote URL" placeholder="https://" />
                <FieldHint>A hint on its own keeps a margin above it.</FieldHint>
            </div>
            <FormError>The remote refused the push.</FormError>
        </div>
    );
}

Type main into the branch name to see the error. An Input or a TextArea inside a Field connects itself. The label points at it with htmlFor, aria-describedby names the hint and the error, and an error sets aria-invalid. Another control needs its own accessible name, since the Field cannot reach inside it.

A group ​

A <label> can point at an input, not at a Segmented, a Select, ChoiceCards or a path in a box with a button beside it. group draws the same label, hint and error with the same spacing, and makes the Field a role="group" its label names and its hint and error describe. The control inside still takes its own accessible name, and an Input inside a group is left unconnected, for an input that is one part of what the group holds.

tsx
<Field group label="Folder" hint="The project is made in a new folder here.">
    <div className="flex items-center gap-2">
        <div className="field flex min-w-0 flex-1 items-center text-text-muted">{path}</div>
        <Button variant="secondary" onClick={choose}>Choose…</Button>
    </div>
</Field>

Beside the control ​

In a dialog with a form of several fields, a stack of labels and controls soon scrolls. orientation="horizontal" puts the label in a column of 112 pixels beside the control, at the size of what is typed, so the controls of every field line up. The hint and the error stay under the control. The label sits on the middle of the control's first line, however tall a TextArea grows.

tsx
import { useState } from 'react';
import { Field, Input, Segmented, TextArea } from '@basmilius/desktop-ui';

export default function FieldHorizontalDemo() {
    const [kind, setKind] = useState('service');

    return (
        <div className="flex w-120 flex-col gap-3">
            <Field orientation="horizontal" label="Name">
                <Input defaultValue="Run server" />
            </Field>
            <Field orientation="horizontal" group label="Kind" hint="Keeps running until you stop it.">
                <Segmented
                    label="Kind"
                    value={kind}
                    onValueChange={setKind}
                    options={[
                        { id: 'service', label: 'Service' },
                        { id: 'task', label: 'Task' }
                    ]}
                />
            </Field>
            <Field orientation="horizontal" label="Command">
                <TextArea rows={2} mono defaultValue="php -S 0.0.0.0:8000 -t public dev/server.php" />
            </Field>
        </div>
    );
}

Field props ​

PropType
childrenReactNodeRequired. The control.
labelReactNodeDrawn as a SectionLabel above the control.
hintReactNodeThe line under the control that says what goes in it.
errorReactNodeShown under the control and marks it invalid. null or '' shows nothing.
groupbooleanLabels a group rather than one control. Default false.
orientation'vertical' | 'horizontal'horizontal puts the label beside the control. Default vertical.
classNamestring
refRef<HTMLDivElement>

FieldHint and FormError ​

FieldHint is a <p> in the hint's style with a 4 pixel margin above it. FormError is a <p> with role="alert", so a screen reader announces it when it appears; use it for what went wrong in a dialog or a form as a whole. Both take className, ref and render.

FieldProps, FieldHintProps and FormErrorProps are exported types.