Grid Components
Components
Style the grid's controls, replace them with your own components, and keep editor and popup behavior intact.
Use your design system's controls throughout the grid, including its editors and popup menus. Choose the customization level that matches your task:
| You want to… | Use |
|---|---|
| Change colors, spacing, or borders | Theme tokens and CSS selectors |
| Replace a control while keeping the grid's behavior | The components prop |
| Build a different editor, such as a date-range picker | Column renderEditor |
The kit is exported from @pretable/react. These examples assume you have
imported an @pretable/ui theme and the grid skin once at your app’s entry point,
as shown in Import the styles. Each preview has a
Code tab with the complete component source.
Replacing a component
Pass a replacement to <PretableSurface> or <Pretable>. Each slot applies
wherever that kit control appears, including inside portals. Omitted slots
keep their built-in components.
import { forwardRef } from "react";
import { PretableSurface, type PretableTextInputProps } from "@pretable/react";
// Define the component outside the grid's render function.
const AppInput = forwardRef<HTMLInputElement, PretableTextInputProps>(
function AppInput({ site, className, ...props }, ref) {
return (
<input
{...props}
ref={ref}
data-pretable-text-input=""
data-pretable-site={site}
className={["app-input", className].filter(Boolean).join(" ")}
/>
);
},
);
<PretableSurface
ariaLabel="Orders"
rows={rows}
columns={columns}
getRowId={(row) => row.id}
components={{ TextInput: AppInput }}
/>;Forward the supplied native props, handlers, ARIA/data attributes, and ref
to the actual control. Preserve className and style when adding your own
styling. The grid uses the ref for editor autofocus, textarea sizing, popup
anchoring, and focus restoration. React 18 requires forwardRef; React 19
also supports a plain ref prop.
Keep each replacement's component identity stable: define it at module scope,
not inside the rendering component. An inline components={{ TextInput: AppInput }}
map is fine; defining a new AppInput function on each render remounts the field
and can lose focus or the caret.
site identifies where a control is used. Fields use cell-editor; editor
actions use number-increment, number-decrement, date-previous-month, and
date-next-month. Branch on site when one placement needs different styling.
PretableBuiltInSite lists the built-in names and may gain new names in minor
releases; PretableSite also accepts application-defined strings.
The slots cover kit controls. Structural column headers, tabs, tree expanders, menu items, and segmented filter joins keep their specialized behavior.
Use your own editor controls
These replacements keep the built-in parsing, keyboard handling, save state, and errors. Open Quantity to use the replacement input and stepper buttons, or Notes to use the replacement textarea. The Code tab contains the complete controlled grid and native replacements.
Native input, textarea, and icon-button replacements preserve built-in editing, including a number stepper, multiline drafts, and delayed saves with a corrected retry.
Replace a button
This example replaces Button across the tool panel and filter dialog, using
site to give Reset columns a different appearance.
The grid's own tool panel and filter dialog, with every Button replaced by the app's — one slot, applied everywhere, branching on site for the one place it treats differently.
The components
All controls forward refs to their native nodes and accept className and
style. Button, IconButton, Select, and Checkbox render <button type="button">
and do not accept a type prop. TextInput and Textarea render native fields.
| Prop | Type | Native ref |
|---|---|---|
Button | PretableButtonComponent | HTMLButtonElement |
IconButton | PretableIconButtonComponent | HTMLButtonElement |
Select | PretableSelectComponent | HTMLButtonElement (trigger) |
TextInput | PretableTextInputComponent | HTMLInputElement |
Textarea | PretableTextareaComponent | HTMLTextAreaElement |
Checkbox | PretableCheckboxComponent | HTMLButtonElement |
Each slot has a standalone export prefixed with Pretable, such as
PretableTextInput, plus corresponding Props and Component types, for example
PretableTextInputProps and PretableTextInputComponent. All accept site,
which the built-in controls emit as data-pretable-site.
Button
A labelled action. variant="ghost" (the default) uses a hover tint;
variant="link" uses plain accent text. Other props are native button props.
Apply a saved view with a ghost button, then reset it with a link button. Disabled states prevent repeating the current action.
| Prop | Type | Meaning |
|---|---|---|
variant | "ghost" | "link" | Appearance; defaults to ghost |
site | PretableSite | Placement name |
IconButton
An icon-only action. Supply its icon as children and a nonempty aria-label
describing the action, such as Next month. The label is required by its type.
Toggle a view’s pinned state with an icon-only button. The accessible name stays the same while aria-pressed and a visible status report the change.
| Prop | Type | Meaning |
|---|---|---|
aria-label | string | Required accessible name |
site | PretableSite | Placement name |
Select
A controlled select-only combobox with a button trigger and a portalled listbox. It differs from the editable enum editor, which accepts typed queries in a text field.
A controlled Select reorders projects. Open it with ArrowDown, skip the disabled option, or type R to find the rich Recently updated label through textValue.
| Prop | Type | Meaning |
|---|---|---|
options | readonly PretableSelectOption[] | Choices with unique, stable value, label, optional disabled, and textValue for rich labels |
value | string | Committed value; displayed as-is if absent from the options |
onChange | (value: string) => void | Called when a different value is chosen |
aria-label | string | Required accessible name |
site | PretableSite | Placement name |
String and number labels supply their own typeahead text. Rich labels need a
plain-text textValue, for example
{ value: "review", label: <strong>In review</strong>, textValue: "In review" }.
| Key | Behavior |
|---|---|
| ArrowDown / ArrowUp, Enter, Space | Open a closed list |
| ArrowDown / ArrowUp | Move through enabled options, wrapping at the ends |
| Home / End | Move to the first / last enabled option |
| Typing | Match a label prefix; reset after 500ms or after closing/reopening |
| Enter / Space | Choose the active option |
| Escape | Close and return focus to the trigger |
| Tab | Close and continue normal focus traversal |
If options change while open, the highlight follows its value through reordering. Removing or disabling that value selects the first enabled highlight. An all-disabled list cannot commit; an empty list closes and stays closed when options are added again.
TextInput
PretableTextInput renders a native <input>. It forwards input props such as
type, inputMode, value, onChange, readOnly, and ARIA attributes. It adds
only site and the data-pretable-text-input styling hook. Debouncing, parsing,
and commit behavior belong to the caller.
A labelled native search input filters a small list as you type. Clear it to show everyone again.
| Prop | Type | Meaning |
|---|---|---|
site | PretableSite | Placement name; other props are native input attributes |
Textarea
PretableTextarea renders a native <textarea> with the same prop and ref
contract as TextInput. It emits data-pretable-textarea. The multiline editor
handles auto-grow and commit keys; the shared control forwards native props
such as rows, value, and onChange.
A controlled textarea uses native rows and maxLength props. Save the note to preview its line breaks.
| Prop | Type | Meaning |
|---|---|---|
site | PretableSite | Placement name; other props are native textarea attributes |
Checkbox
A controlled <button role="checkbox"> with aria-checked set to true,
false, or mixed. Space and Enter activate it through the native click.
Controlled checkboxes share a selected-project list. Select all starts mixed, selects every project on activation, then clears the list on the next activation.
| Prop | Type | Meaning |
|---|---|---|
checked | boolean | "mixed" | Current state; mixed toggles to true |
onCheckedChange | (next: boolean) => void | Requests a state change; the caller must update checked |
site | PretableSite | Placement name |
Native button props pass through. A supplied onClick runs first and can
call preventDefault() to prevent the toggle.
Accessible names and refs
Give every control an accessible name. Use visible text for Button, aria-label
for IconButton and Select, and a native label or ARIA label for fields and
Checkbox. Missing-name warnings check common naming sources once after mounting;
they do not replace accessibility testing.
Built-in controls support object refs and callback refs. Select, TextInput,
Textarea, and Checkbox compose refs internally: on detach they run a returned
cleanup, otherwise call the ref with null, and clear object refs. Button and
IconButton forward refs directly; returned cleanups require React 19.
Styling
Use theme tokens for shared appearance. The built-in controls also expose these selectors. Native replacements can emit the same hooks to retain the skin's styles, as the input example above does.
| Selector | Target |
|---|---|
[data-pretable-button], [data-pretable-icon-button] | Action buttons |
[data-pretable-variant="link"] | A labelled button variant |
[data-pretable-site="tool-reset"] | A specific placement |
[data-pretable-text-input], [data-pretable-textarea] | Native fields |
[data-pretable-select] | Select trigger; data-pretable-value holds its committed selection |
[data-pretable-select-label], [data-pretable-listbox] | Select label and popup |
[data-pretable-option] | Option; data-pretable-option-value identifies it |
[data-pretable-option][data-pretable-active] | Keyboard-highlighted option |
[data-pretable-option][aria-selected="true"] | Chosen option |
[data-pretable-checkbox][aria-checked="mixed"] | Mixed checkbox state |
Checkbox is a button, so use aria-checked selectors instead of :checked.
Select's data-pretable-value is independent of the native button's value.
Placement-specific hooks such as data-pretable-filter-clear remain available.
[data-pretable-site="tool-reset"] {
color: var(--pretable-text-error);
}
[data-pretable-checkbox][aria-checked="mixed"] {
--pretable-checkbox-checked-bg: var(--pretable-accent);
}Overlay scope
Popups render into document.body by default to escape the grid's clipping
viewport. React context crosses the portal, but inherited CSS comes from the
popup's DOM ancestors. For a local theme, use PretableOverlayProvider to
place popups inside the same theme scope:
import { PretableOverlayProvider } from "@pretable/react";
import { useState, type ReactNode } from "react";
function ScopedGrid({ children }: { children: ReactNode }) {
const [container, setContainer] = useState<HTMLDivElement | null>(null);
return (
<section className="dashboard" dir="ltr">
<PretableOverlayProvider container={container}>
{children}
</PretableOverlayProvider>
<div ref={setContainer} />
</section>
);
}Put the grid inside ScopedGrid and token overrides on .dashboard. The grid
and popup host inherit its tokens, direction, and color scheme. The provider
does not copy styles from the trigger.
| Prop | Type | Meaning |
|---|---|---|
container | HTMLElement | null | Required target; nearest provider wins. null defers popup content until a target exists |
children | ReactNode | Grid or kit controls that use this target |
Keep the host connected, in the trigger's document, and outside clipping or transformed containing blocks. Replacing the host remounts its popup content and does not preserve focus. A null target retains open intent; focus-taking menus receive focus when the host attaches.
Nested popups dismiss from the inside out with Escape. Opening a sibling Select closes the previous list. Outside presses dismiss without returning focus to the trigger.
Migration notes
For older kit integrations, add textValue to rich Select labels.
PretableSelectOption is a union; extend it with an intersection such as
type AppOption = PretableSelectOption & { category: string }.
Update option selectors from data-active to data-pretable-active.