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.

accessible-icon-action.tsxtsx
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.

button-patterns.tsxtsx
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.

contact-fields.tsxtsx
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.

invoice-table.tsxtsx
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.

server-sort.tsxtsx
<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.

confirm-dialog.tsxtsx
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

button.test.tsxtsx
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();
});