Shared TypeScript type definitions for React Spectrum components and hooks, providing common interfaces for DOM interactions, styling, accessibility, internationalization, and component behavior across the React Spectrum ecosystem
—
Label positioning, alignment, and form-related properties for consistent labeling patterns across React Spectrum components.
Core types for label positioning and alignment.
/**
* Label position relative to the labeled element
*/
type LabelPosition = "top" | "side";
/**
* Label alignment within its container
*/
type Alignment = "start" | "end";
/**
* How to indicate required fields
*/
type NecessityIndicator = "icon" | "label";Basic labeling interface for components that need labels.
/**
* Basic properties for labelable components
*/
interface LabelableProps {
/** The content to display as the label */
label?: ReactNode;
}Extended labeling interface with Spectrum-specific positioning and styling options.
/**
* Spectrum-specific labelable properties with positioning and styling
*/
interface SpectrumLabelableProps extends LabelableProps {
/**
* The label's overall position relative to the element it is labeling.
* @default "top"
*/
labelPosition?: LabelPosition;
/**
* The label's horizontal alignment relative to the element it is labeling.
* @default "start"
*/
labelAlign?: Alignment;
/**
* Whether the required state should be shown as an icon or text.
* @default "icon"
*/
necessityIndicator?: NecessityIndicator;
/**
* Whether the label is labeling a required field or group.
*/
isRequired?: boolean;
/**
* A ContextualHelp element to place next to the label.
*/
contextualHelp?: ReactNode;
}Usage Examples:
import {
LabelableProps,
SpectrumLabelableProps,
LabelPosition,
Alignment,
NecessityIndicator
} from "@react-types/shared";
// Basic labelable component
interface TextFieldProps extends LabelableProps {
value?: string;
onChange?: (value: string) => void;
}
function BasicTextField({ label, value, onChange }: TextFieldProps) {
return (
<div>
{label && <label>{label}</label>}
<input
value={value}
onChange={(e) => onChange?.(e.target.value)}
/>
</div>
);
}
// Spectrum labelable component with positioning
interface SpectrumTextFieldProps extends SpectrumLabelableProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
}
function SpectrumTextField({
label,
labelPosition = "top",
labelAlign = "start",
necessityIndicator = "icon",
isRequired,
contextualHelp,
value,
onChange,
placeholder
}: SpectrumTextFieldProps) {
const isLabelSide = labelPosition === "side";
const alignmentClass = labelAlign === "end" ? "label-end" : "label-start";
return (
<div className={`field ${isLabelSide ? "field-horizontal" : "field-vertical"}`}>
{label && (
<div className={`label-container ${alignmentClass}`}>
<label>
{label}
{isRequired && (
necessityIndicator === "icon" ?
<span className="required-icon">*</span> :
<span className="required-text"> (required)</span>
)}
</label>
{contextualHelp}
</div>
)}
<input
value={value}
onChange={(e) => onChange?.(e.target.value)}
placeholder={placeholder}
required={isRequired}
/>
</div>
);
}
// Form group with labels
interface FormGroupProps extends SpectrumLabelableProps {
children: React.ReactNode;
}
function FormGroup({
label,
labelPosition = "top",
labelAlign = "start",
isRequired,
contextualHelp,
children
}: FormGroupProps) {
return (
<fieldset>
{label && (
<legend className={`legend-${labelAlign}`}>
{label}
{isRequired && <span className="required-icon">*</span>}
{contextualHelp}
</legend>
)}
{children}
</fieldset>
);
}
// Usage examples
function LabelExamples() {
return (
<div>
{/* Basic top-aligned label */}
<SpectrumTextField
label="Name"
labelPosition="top"
labelAlign="start"
isRequired
placeholder="Enter your name"
/>
{/* Side-aligned label with contextual help */}
<SpectrumTextField
label="Email"
labelPosition="side"
labelAlign="end"
contextualHelp={<button>?</button>}
placeholder="Enter your email"
/>
{/* Required field with text indicator */}
<SpectrumTextField
label="Password"
isRequired
necessityIndicator="label"
placeholder="Enter password"
/>
{/* Form group with legend */}
<FormGroup
label="Contact Information"
labelAlign="start"
isRequired
>
<SpectrumTextField label="Phone" />
<SpectrumTextField label="Address" />
</FormGroup>
</div>
);
}
// Custom hook for label configuration
interface UseLabelConfigOptions {
defaultPosition?: LabelPosition;
defaultAlign?: Alignment;
defaultNecessityIndicator?: NecessityIndicator;
}
function useLabelConfig(options: UseLabelConfigOptions = {}) {
const {
defaultPosition = "top",
defaultAlign = "start",
defaultNecessityIndicator = "icon"
} = options;
return {
getLabelProps: (props: Partial<SpectrumLabelableProps>) => ({
labelPosition: props.labelPosition ?? defaultPosition,
labelAlign: props.labelAlign ?? defaultAlign,
necessityIndicator: props.necessityIndicator ?? defaultNecessityIndicator,
...props
})
};
}
// Using the hook
function ConfigurableForm() {
const { getLabelProps } = useLabelConfig({
defaultPosition: "side",
defaultAlign: "end"
});
return (
<form>
<SpectrumTextField
{...getLabelProps({
label: "Username",
isRequired: true
})}
/>
<SpectrumTextField
{...getLabelProps({
label: "Bio",
labelPosition: "top" // Override default
})}
/>
</form>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-types--shared