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 bordersTheme tokens and CSS selectors
Replace a control while keeping the grid's behaviorThe components prop
Build a different editor, such as a date-range pickerColumn 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.

tsx
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.

Use your own editor controls

Native input, textarea, and icon-button replacements preserve built-in editing, including a number stepper, multiline drafts, and delayed saves with a corrected retry.

.md

Replace a button

This example replaces Button across the tool panel and filter dialog, using site to give Reset columns a different appearance.

Replacing a component

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.

.md

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.

PropTypeNative ref
ButtonPretableButtonComponentHTMLButtonElement
IconButtonPretableIconButtonComponentHTMLButtonElement
SelectPretableSelectComponentHTMLButtonElement (trigger)
TextInputPretableTextInputComponentHTMLInputElement
TextareaPretableTextareaComponentHTMLTextAreaElement
CheckboxPretableCheckboxComponentHTMLButtonElement

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.

Saved view actions

Apply a saved view with a ghost button, then reset it with a link button. Disabled states prevent repeating the current action.

.md
PropTypeMeaning
variant"ghost" | "link"Appearance; defaults to ghost
sitePretableSitePlacement 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.

Pin a view

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.

.md
PropTypeMeaning
aria-labelstringRequired accessible name
sitePretableSitePlacement 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.

Sort a project list

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.

.md
PropTypeMeaning
optionsreadonly PretableSelectOption[]Choices with unique, stable value, label, optional disabled, and textValue for rich labels
valuestringCommitted value; displayed as-is if absent from the options
onChange(value: string) => voidCalled when a different value is chosen
aria-labelstringRequired accessible name
sitePretableSitePlacement 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" }.

KeyBehavior
ArrowDown / ArrowUp, Enter, SpaceOpen a closed list
ArrowDown / ArrowUpMove through enabled options, wrapping at the ends
Home / EndMove to the first / last enabled option
TypingMatch a label prefix; reset after 500ms or after closing/reopening
Enter / SpaceChoose the active option
EscapeClose and return focus to the trigger
TabClose 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.

Search teammates

A labelled native search input filters a small list as you type. Clear it to show everyone again.

.md
PropTypeMeaning
sitePretableSitePlacement 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.

Write a handoff note

A controlled textarea uses native rows and maxLength props. Save the note to preview its line breaks.

.md
PropTypeMeaning
sitePretableSitePlacement 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.

Select projects

Controlled checkboxes share a selected-project list. Select all starts mixed, selects every project on activation, then clears the list on the next activation.

.md
PropTypeMeaning
checkedboolean | "mixed"Current state; mixed toggles to true
onCheckedChange(next: boolean) => voidRequests a state change; the caller must update checked
sitePretableSitePlacement 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.

SelectorTarget
[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.

css
[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:

tsx
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.

PropTypeMeaning
containerHTMLElement | nullRequired target; nearest provider wins. null defers popup content until a target exists
childrenReactNodeGrid 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.