Component API Patterns
Boreal UI components use consistent React patterns across the core and Next builds. Start with semantic props, controlled state where needed, typed data models, and public styling hooks.
Semantic first
Prefer visible labels, captions, native element semantics, and ARIA props that describe real relationships in the interface.
Public behavior props
Use component props for open state, sorting, selected values, disabled state, and loading state instead of reaching into internals.
Stable styling hooks
Style through root and section class props. Avoid generated SCSS module class names from the Next build.
Accessibility Props
Components provide useful ARIA pass-throughs, but the consuming app still chooses meaningful names and descriptions.
import { IconButton } from "@boreal-ui/core";
function TrashIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 3h6l1 2h4v2H4V5h4l1-2Z" fill="currentColor" />
</svg>
);
}
export function DeleteAction() {
return (
<IconButton
icon={TrashIcon}
aria-label="Delete project"
theme="secondary"
state="warning"
/>
);
}Label every control
- Use visible labels for form fields whenever possible.
- Use aria-label or aria-labelledby for icon-only controls.
- Use aria-describedby for helper text, errors, and dialog details.
Preserve component semantics
- Pass state through the component API.
- Keep focus styles visible when overriding CSS variables.
- Use captions or accessible names for DataTable instances.
Buttons and Links
Button supports actions, links, and custom render targets while keeping a consistent visual API.
import { Button } from "@boreal-ui/core";
export function ButtonExamples() {
return (
<>
<Button type="submit">Save</Button>
<Button href="/settings" theme="secondary">Open settings</Button>
<Button href="https://example.com" isExternal>External docs</Button>
</>
);
}Forms
Text inputs, text areas, selects, checkboxes, radios, toggles, sliders, date pickers, file uploads, and tag inputs follow normal React controlled and uncontrolled patterns.
import { useState } from "react";
import { Select, TextArea, TextInput } from "@boreal-ui/core";
export function ContactFields() {
const [priority, setPriority] = useState("low");
return (
<>
<TextInput label="Name" name="name" autoComplete="name" />
<Select
label="Priority"
name="priority"
value={priority}
onChange={setPriority}
options={[
{ label: "Low", value: "low" },
{ label: "High", value: "high" },
]}
/>
<TextArea label="Notes" name="notes" helperText="Add useful context." />
</>
);
}Typed DataTable
Define your row type once, create typed columns, and pass stable row keys for sortable or interactive tables.
import { DataTable } from "@boreal-ui/core";
import type { Column } from "@boreal-ui/types";
type Invoice = {
id: string;
customer: string;
total: number;
status: "paid" | "open";
};
const columns: Column<Invoice>[] = [
{ key: "customer", label: "Customer", sortable: true, isRowHeader: true },
{
key: "total",
label: "Total",
sortable: true,
render: (value) => `$${Number(value).toFixed(2)}`,
},
{ key: "status", label: "Status" },
];
export function InvoiceTable({ rows }: { rows: Invoice[] }) {
return (
<DataTable
caption="Invoices"
columns={columns}
data={rows}
rowKey={(row) => row.id}
defaultSortKey="customer"
striped
wrapCells
/>
);
}Server-side sorting
Use serverSort with onSortChange when your API owns sorting. This keeps DataTable as the presentation and interaction layer.
<DataTable
aria-label="Invoices"
columns={columns}
data={rows}
serverSort
onSortChange={(key, order) => {
fetchInvoices({ sortBy: String(key), order });
}}
/>Overlays and Interactive Components
Modal, Dropdown, PopOver, Tooltip, Tabs, Accordion, CommandPalette, and NotificationCenter include keyboard and ARIA behavior.
import { Button, Modal } from "@boreal-ui/core";
export function ConfirmDialog({
open,
onClose,
}: {
open: boolean;
onClose: () => void;
}) {
return (
<Modal
open={open}
onClose={onClose}
title="Delete project"
aria-describedby="delete-project-description"
>
<div>
<p id="delete-project-description">
This action cannot be undone.
</p>
<Button state="warning">Delete</Button>
</div>
</Modal>
);
}State and validation
Use required, disabled, helperText, errorMessage, and invalid ARIA state where each component supports them.
Stable test IDs
Prefer Testing Library role queries, then use data-testid where a stable selector is still useful.
Consumer class hooks
Use public class props such as className, table classes, row class callbacks, and section classes for local styling.
Testing Pattern
import { render, screen } from "@testing-library/react";
import { Button } from "@boreal-ui/core";
it("submits the form", () => {
render(<Button data-testid="save-action">Save</Button>);
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument();
expect(screen.getByTestId("save-action")).toBeInTheDocument();
});