Validation
View sourcePer-column sync or async validate runs before each commit. Rejected commits keep the editor open, surface the error in a popover, and fire a flash on the cell so sighted users know why their input was bounced.
Try it
- SKU is sync-validated against the pattern
SKU-NNN - Name is async-validated (typing "duplicate" rejects after a 600ms simulated round trip)
- Stock must be a non-negative integer; Price must be > 0
Known SKUs: SKU-001, SKU-002, SKU-003, SKU-004, SKU-005
Try this
Each column has different validation rules — try to break each one.
- SKU: type something that's not
SKU-NNNand tab out — sync rejection. - Name: clear it and tab — required check fails. Type "duplicate" and tab — async rejection after ~600ms.
- Price: type 0 or a negative number — rejected.
- Stock: type a decimal — rejected (integers only).
The contract
A column validator returns either { ok: true } or { ok: false, message }. It can be sync, or it can return a Promise. The async signature receives an AbortSignal; if the user types another keystroke before the previous validate resolves, the grid aborts the in-flight check.
Editor states
- dirty — the input differs from
previousValue. Composes with the cell's normal background. - pending — async validate is in flight. The editor stays open with a subtle spinner-ish state while the check resolves.
- error — last validate returned
ok: false. The editor stays open, the errormessagerenders in a popover, and the cell flashes once. - All three states are surfaced via a single canonical
data-bc-grid-edit-stateDOM attribute and CSS variables — style them however you want.
Where validation runs
- Editor side first.The editor itself rejects shapes it can't commit (non-numeric number, malformed date). The cell stays open.
- Column validate next. Sync first, then async. AbortSignal is honoured.
- onCellEditCommit last. The fire-and-forget happy path; the result-shaped opt-in lets the commit handler (e.g. server round trip) reject too.