Outlined field
The outlined box with the field's name on its top edge that the maritime inputs draw in.
On this page
Code
import { FormField } from "@bosunkit/components/form-field";
import { OutlinedField, OutlinedInput } from "@bosunkit/components/outlined-field";
import { CircleHelp, Ship } from "lucide-react";
import { useState, type ChangeEvent, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"A vessel's registration: a required name the crew fill, a call sign that is wrong until it has four to seven " +
"letters and digits, a read-only home port, a disabled tonnage, a field with an icon at the start and a help " +
"button at the end that shows its help, and the same input inside a form field.";
function callSignError(callSign: string): string | undefined {
return callSign === "" || /^[A-Z0-9]{4,7}$/.test(callSign)
? undefined
: "A call sign has 4 to 7 letters and digits, such as MXYZ7. Check it on the ship radio licence.";
}
export default function Showcase(): ReactElement {
const [name, setName]: [string, Dispatch<SetStateAction<string>>] = useState<string>("Althea");
const [callSign, setCallSign]: [string, Dispatch<SetStateAction<string>>] = useState<string>("MX7");
const [helpShown, setHelpShown]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
return (
<div
style={{
width: "100%",
maxWidth: 560,
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
gap: "var(--space-lg)",
}}
>
<OutlinedInput
label="Vessel name"
required
value={name}
onChange={(event: ChangeEvent<HTMLInputElement>): void => {
setName(event.target.value);
}}
/>
<OutlinedInput
label="Call sign"
hint="Four to seven letters and digits."
value={callSign}
error={callSignError(callSign)}
autoCapitalize="characters"
onChange={(event: ChangeEvent<HTMLInputElement>): void => {
setCallSign(event.target.value.toUpperCase());
}}
/>
<OutlinedInput label="Home port" defaultValue="Falmouth" readOnly />
<OutlinedInput label="Gross tonnage" defaultValue="18" disabled />
<OutlinedField
label="Hull identification number"
labelFor="showcase-hin"
filled
start={<Ship size={18} aria-hidden="true" />}
end={
<button
type="button"
className="outlined-field__icon-button"
aria-label="Help"
aria-expanded={helpShown}
onClick={(): void => {
setHelpShown(!helpShown);
}}
>
<CircleHelp size={20} aria-hidden="true" />
</button>
}
hint={helpShown ? "Fourteen characters, moulded into the transom on the starboard side." : undefined}
hintId="showcase-hin-help"
>
<input
id="showcase-hin"
className="outlined-field__control"
defaultValue="GB-HAN46012K526"
aria-describedby={helpShown ? "showcase-hin-help" : undefined}
/>
</OutlinedField>
<FormField label="Owner">
<OutlinedInput aria-label="Owner" defaultValue="Mara Quist" />
</FormField>
</div>
);
}
An outlined field is a box with the field's name in a gap on its top edge. The maritime inputs (MMSI, position,
date of birth, calendar, email, phone and the crew picker) all draw in it, so a form built from them reads as one
family. OutlinedInput is a text input in the box; OutlinedField is the box alone, around one control or a group
of them: a select's trigger, a combo box's input, a text area, a time.
Every field of a form draws outlined: the schema form draws each of its fields so, and a form built by hand does the same, so a form reads as one family and its rows lose the line a label above each box takes. A switch has no box and stands with its name beside it. A plain input is for a single field outside a form: a search, a filter above a table, a setting saved on its own, a value in a toolbar.
The label rests in the middle of an empty field and moves onto the edge when the field holds a value or the focus,
by a transform on the motion tokens, and at once under reduced motion. A required field
shows a red asterisk the accessible name leaves out. Under the box sits a hint, or the error in its place: the
outline and the label turn red, the control is marked aria-invalid, and the error is a role="alert" the control
is described by. The box is as high as an input beside it: 48 px, the touch target the kit
sets, on a phone or a tablet (a screen up to the 68.75em breakpoint) or under a coarse pointer, and 44 px on a desktop
with a fine pointer. Without a label the box names nothing, so the input takes aria-label or sits in a
form field.
Under the box, status shows a quiet line in the hint's place, read out politely (role="status"): the maritime
inputs say there what a paste took or why it took nothing. Each of them shows a paste button first among its trailing
icons when the clipboard holds a value it can take. The trailing icons sit close together at the right edge, each
with a 48 px target reaching out on its outer side.
The label and field body share the field's fill in every state, read-only and disabled included: the same background the plain input beside it draws, so a field on a page, card or panel reads as an input on that surface, and the floating label is centered in the outline's gap.
Examples
Empty
Code
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string =
"An empty field: the label rests inside the outline until the field takes focus, then moves onto its edge and " +
"the placeholder shows.";
export default function Empty(): ReactElement {
return <OutlinedInput label="Given names" placeholder="e.g. MARIA LOPEZ" />;
}
Filled
Code
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string = "A field with a value: the label sits in a gap in the outline's top edge.";
export default function Filled(): ReactElement {
return <OutlinedInput label="Family name" defaultValue="Quist" />;
}
Required
Code
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string =
"A required field: a red asterisk after the label, which the accessible name leaves out, and `required` on the input.";
export default function Required(): ReactElement {
return <OutlinedInput label="Given names" required />;
}
Invalid
Code
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string =
"An error turns the outline and label red, marks the input `aria-invalid` and is announced; it takes the hint's place.";
export default function Invalid(): ReactElement {
return (
<OutlinedInput
label="Passport number"
defaultValue="X12"
hint="As printed on the passport's photo page."
error="This passport number is too short. Check it against the photo page."
/>
);
}
Read-only
Code
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string = "A read-only field draws a dashed outline; the value can be selected and copied.";
export default function ReadOnly(): ReactElement {
return <OutlinedInput label="Home port" defaultValue="Falmouth" readOnly />;
}
Disabled
Code
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string = "A disabled field is dimmed and takes no focus.";
export default function Disabled(): ReactElement {
return <OutlinedInput label="Gross tonnage" defaultValue="18" disabled />;
}
Inside a form field
Code
import { FormField } from "@bosunkit/components/form-field";
import { OutlinedInput } from "@bosunkit/components/outlined-field";
import type { ReactElement } from "react";
export const description: string =
"Inside a form field, the field draws no label of its own and takes its name from `aria-label`; the form field " +
"carries the error.";
export default function InFormField(): ReactElement {
return (
<FormField label="Owner" error="Enter the owner's name as on the registration.">
<OutlinedInput aria-label="Owner" aria-invalid />
</FormField>
);
}
Props
OutlinedInput
A text input in the outlined box. It takes every attribute of a native input as well.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | – | Additional control classes. |
| label | string | undefined | – | The field's name, drawn on the outline's top edge. |
| hint | ReactNode | – | Text under the field that helps fill it, translated by the caller. |
| error | string | undefined | – | What is wrong with the value and how to fix it. Sets aria-invalid and is announced when it appears. |
| status | FieldStatus | undefined | – | A quiet line in the hint's place; see OutlinedField. |
| start | ReactNode | – | Content before the input inside the outline. |
| end | ReactNode | – | Content after the input inside the outline. |
| fieldClassName | string | undefined | – | Classes added to the field's root. |
| testId | string | undefined | – | data-testid on the field. |
| ref | Ref<HTMLInputElement> | undefined | – | The input element. |
| boxRef | RefObject<HTMLDivElement | null> | undefined | – | The outlined box, for a popover to open under. |
| motion | MotionProp | undefined | – | How the error line moves; see OutlinedField. |
| onValueChange | ((value: string, eventDetails: Input.ChangeEventDetails) => void) | undefined | – | Callback fired when the value changes. Use when controlled. |
| defaultValue | Field.Control.Props['defaultValue'] | undefined | – | The default value of the input. Use when uncontrolled. |
| value | React.ComponentProps<'input'>['value'] | undefined | – | The value of the input. Use when controlled. |
| render | React.ReactElement | ComponentRenderFn<RenderFunctionProps, State> | undefined | – | Allows you to replace the component's HTML element
with a different tag, or compose it with another component.
Accepts a ReactElement or a function that returns the element to render. |
| style | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) | undefined | – | Style applied to the element, or a function that returns a style object based on the component's state. |
Also takes every attribute of Omit<BaseInput.Props, "children" | "className">.
OutlinedField
The box alone. labelFor makes the label a <label> for one control; labelId names a group through
aria-labelledby.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | undefined | – | The field's name, drawn on the outline's top edge; omitted when a Label or FormField names the control. |
| labelFor | string | undefined | – | The id of the control the label names: the label is then a <label for>. |
| labelId | string | undefined | – | The label's own id, for a group of controls that names itself with aria-labelledby. |
| required | boolean | undefined | – | Whether the field must be filled: a red asterisk after the label. Set required on the control as well. |
| invalid | boolean | undefined | – | Whether the value is wrong: a red outline and label. The control sets aria-invalid itself. |
| disabled | boolean | undefined | – | Whether the field takes no input: dimmed. |
| readOnly | boolean | undefined | – | Whether the field shows its value without letting the crew change it: a dashed outline. |
| filled | boolean | undefined | – | Whether the field holds a value, so the label rests on the outline; it also rests there while focused. |
| hint | ReactNode | – | Text under the field that helps fill it, translated by the caller. |
| hintId | string | undefined | – | The hint's id, for the control's aria-describedby. |
| error | string | undefined | – | What is wrong with the value and how to fix it; replaces the hint and is announced when it appears. |
| errorId | string | undefined | – | The error's id, for the control's aria-describedby. |
| status | FieldStatus | undefined | – | A quiet line in the hint's place, such as what a paste took or why it took nothing; read out politely
(role="status"). The error, when there is one, shows in its place. |
| start | ReactNode | – | Content before the control inside the outline, such as an icon or a country code. |
| end | ReactNode | – | Content after the control inside the outline, such as a help or a calendar button. |
| childrenrequired | ReactNode | – | The control or controls. |
| after | ReactNode | – | Content under the outline, before its error or hint, such as the files a file input holds. |
| className | string | undefined | – | Classes added to the field's own. |
| testId | string | undefined | – | data-testid on the field. |
| ref | Ref<HTMLDivElement> | undefined | – | The field's root element. |
| boxRef | RefObject<HTMLDivElement | null> | undefined | – | The outlined box, for a popover to open under. |
| motion | MotionProp | undefined | – | How the error line moves. By default, as a FormField's, an error that appears rises in and fades out when the
value is fixed; one there on the first render does not move. A preset or { enter, exit, change } chooses;
false turns it off. The error is announced and readable from its first frame. |
Related
- Form field: the label above an input, for forms in the kit's other style.
- Input: the plain text input.
- Accessibility: target sizes and accessible names.