# Data grid

> Many records at once, a row each and a column for each field, moved through, selected and edited in place.

A data grid shows many records at once, a row for each and a column for each of
their fields, and lets people move through them, select them and edit them in
place. It is where data is browsed and corrected in bulk.

## When to use

Use a data grid when people compare records, scan a column, or change many
values. For one record with many fields, an
[object editor](https://ui.publictheta.com/components/object-editor.md) gives each field room. For a list to
choose from, a [listbox](https://ui.publictheta.com/components/listbox.md) is lighter.

## Best practices

**Show each value as its kind.** Numbers aligned to compare, in the reader's
locale; dates as they are written; a choice by its label; a yes-or-no as a
checkbox. The columns and their kinds come from the records'
[schema](https://ui.publictheta.com/foundations/schema.md).

**Stay immediate, whatever the size.** Draw the rows in view and no others, so
that a hundred thousand rows scroll and respond as a hundred do. Tell assistive
technology the true number of rows and each row's place among them. A browser
will not lay out an element more than a few million pixels tall; past that, let
the scroll position stand for a proportion of the way through, so that the last
row can still be reached.

**Edit in place.** Simply starting to type replaces the cell's contents; Return,
F2 or a double click opens the cell to work within its text, with the caret at
its end. Return commits and moves down, Tab commits and moves right — on to the
next row from the end of one — and Escape abandons the edit. After Tabs across a
row, Return goes to the next row in the column the Tabs began in, so that
records can be entered one after another.

**Let the arrows do what the edit calls for.** In an edit begun by typing over a
cell, an arrow key commits and moves on, as in a spreadsheet. In a cell opened
to work within its text, the arrows move the caret. F2 goes from the one to the
other. An arrow never changes a number or a date in a cell: stepping belongs to
a field on its own.

**Type straight into the cell.** Focus is on the active cell's own control, so
that what is typed is typed into it directly: an input method composes from its
first key, and characters reached with Option or AltGr begin an edit like any
other.

**Read what is typed as its kind.** The cell reads what is typed as the field
for its kind would — arithmetic in a number, a date in whatever form comes to
hand — and a choice is chosen by typing the start of its name.

**Never lose what was typed.** Text that cannot be read as a value keeps the
cell in editing, marked, and says why. Return, Tab, an arrow and a press on
another cell all leave it open; only Escape abandons it. If focus leaves the
grid, the text stays as typed for the person to come back to.

**Mark what the schema does not allow.** A cell holding a value its field does
not allow — nothing, where something is required; a number outside its range —
is marked and says why. The grid does not refuse to hold it: whether such
records may be saved is for the application to decide.

**Mark what the owner knows is wrong, too.** Some problems no schema can state
— a key used twice, a name the server refused. Let the application say where
they are, by record and field, and mark those cells and say why exactly as the
cells the schema does not allow are marked. A problem the schema states is the
one a cell says first.

**Show what cannot be changed, and pass over it.** A column whose field is
read-only is shown, selected and copied like any other; it says that it is
read-only; typing does not edit it, and clearing, pasting and filling pass over
it and change the columns beside it.

**Keep what is computed right, and show that it is.** A column whose
[formula](https://ui.publictheta.com/foundations/formula.md) computes it follows what it reads: every change
the grid makes — typing, pasting, filling, clearing, adding a record, an undo —
goes out with every value it moves, as one change, and only what the change
reaches is computed again. The column's header says it is computed and as
what; a formula that cannot be used is said on its header, which is marked.
Records are shown as they are given: what the application gives, it has
computed, and the grid makes right what its own edits reach.

**Let cells be [locked](https://ui.publictheta.com/foundations/derived.md), and say which are.** Typing over
a computed cell freezes it at what was typed — but a paste or a fill that
writes what the cell would come to anyway leaves it following. The cells' menu
offers, on a computed cell, _Hold value_ and _Freeze_; on a frozen one, _Hold
value_ and _Unfreeze_, which computes it again; on a held one, _Unlock_ — and on
a range the same, for every cell it applies to, as one step to undo. Where the
application works with locks every cell offers them, and elsewhere only the
computed ones. A held cell is shown, selected and copied, and not edited, and
says why. An edit that would move a held value is not made: the cell keeps what
was typed, marked, with the reason — "Amount is held; this would change it." —
said aloud; Escape puts it aside, and Return asks for it again, which a lock
taken off since lets through. A paste, a fill or a clear does what it may,
leaves what locks refuse, and says once how many cells were refused and whose
locks refused them.

**Know a record by what it is, for its locks.** A lock belongs to a record, not
to a place in the table. Sorting moves no record, and the grid moves locks with
the records it adds and removes itself; but where the application adds, removes
or reorders records on its own, give each record a key that stays with it — its
id — or a lock stays at a place another record has come to.

**Change a yes-or-no directly.** Space, or a click on the checkbox, changes it;
there is nothing to type.

**Fill a selection at once.** With several cells selected, Command-Return gives
what was typed to every one of them, each reading it as its own column does.

**Fill from what is beside.** Under a mouse, the selection has a small handle
at its end corner: dragged, it fills the cells it passes over from the
selection — repeating it, or continuing it where numbers or dates leave one way
to continue them and no other; see [Filling](https://ui.publictheta.com/foundations/fill.md). Command-D
fills the selection down from its first row, and Command-R across from its first
column. Fill series, in the menu, continues what the values at the selection's
start make through the rest of it, as the handle would: the same series for a
pointer that has no handle. A fill is one change; it leaves read-only cells as
they were and says how many it left.

**Offer a menu of what can be done.** A menu on the selection — cut, copy,
paste, clear, fill down, across and as a series, insert rows above, delete rows — puts every
command within reach of a pointer with no keyboard, and names each for
assistive technology. It opens on a secondary press, on Shift with F10 or the
Menu key against the active cell, and under a finger that rests. What cannot be
done where it was asked stays in the menu and cannot be chosen, so that the menu
is the same wherever it opens. Deleting rows from the menu removes every row the
selection is in, and the item says how many: the menu names what it will do,
where a key has to be guarded. While a cell is being edited, the menu asked for
is its field's own. A page may copy to the clipboard only when the browser
allows, and read from it more rarely still: where it does not, change nothing
and say that the keyboard's own copy and paste will.

**Work as a spreadsheet does on the clipboard.** Copy, cut and paste exchange
cells with other applications, as text and as a table; see
[Values as text](https://ui.publictheta.com/foundations/schema-text.md). What a cut clears is exactly what it
copied. A block pasted over a selection it divides evenly is repeated across it.
A clipboard that holds no text changes nothing. Say what a paste did: how many
cells it filled, and how many it left because their text could not be read.

**Let each column be as wide as its contents ask.** A column's edge, in its
header, is dragged to resize it, and pressed twice to fit the column to its name
and to what its cells hold. A column is never made too narrow to press. From
the keyboard, Alt and Shift with an arrow to the side move the edge of the
active cell's column, or of the column whose header has focus. Right to left,
the directions are mirrored.

**Reach the headers from the cells.** The up arrow from a column's first cell
goes to the column's header, and the down arrow comes back; along the headers,
the arrows, Home and End move from one to the next, and Escape returns to the
cell that was left. A spreadsheet's own headers cannot be reached by key, and
its sorting and sizing live in menus elsewhere; a grid whose headers sort,
resize and move has to let the keyboard and assistive technology get to them,
which is what the grid pattern of the ARIA practices asks. Only the plain up
arrow leaves the cells — with Shift it extends the selection, with the command
key it stops at the first row — so nothing a spreadsheet's user does by habit
lands there by surprise. A header takes focus on its own control, the button
that sorts: Return or Space sorts, Alt and Shift with an arrow to the side move
the column's edge, and the Menu key opens the column's menu — sort either way,
fit to its contents, move. Tab from a header goes on from the grid, which stays
one stop. A grid with no rows is still that stop, and an arrow key there goes
to its first header: the columns can be sorted and moved before there is
anything in them.

**Let the columns be put in any order.** A header dragged past the middle of
its neighbour takes its column there, with a line showing where it would land;
held near an edge of the view, the columns scroll to it, as far in a second on
a slow machine as on a fast one; on a header, Alt with an arrow to the side moves the column one place and says
where it now is; see [Reordering](https://ui.publictheta.com/foundations/reorder.md). The order is the
viewer's, kept apart from the records and their schema. The active cell and the
selection go with their columns, and what was done before the move is still
undone in the cells it was done to.

**Sort by any column, without moving a record.** Each column's header has a
button that sorts by it — ascending, then descending, then not at all — and
shows which way. The records stay as they are kept; a row does not jump as it
is edited; see [Sorting a view](https://ui.publictheta.com/foundations/view-order.md).

**Number the rows, and let a row be selected by its number.** A header at the
start of each row shows its place, selects the row when pressed — with Shift,
the rows from the active cell's to it — and stays in view as the columns scroll.
The corner where the headers meet selects everything.

**End in a row that is not yet a record.** Where records can be added, a row
waits after the last one. The arrows step into it; typing in it makes a record,
and another row waits after that. Nothing in it is marked as missing. A pasted
block that runs past the last record makes records to hold the rest. Moving to
an edge, and selecting a column or everything, stop at the last record.

**Remove rows only when rows are what is selected.** Delete empties cells. With
the command key, Backspace removes the rows that are selected whole — and no
row of which only some cells are selected — and says how many.

**Make every change undoable.** Editing, clearing, cutting, pasting, filling,
adding records and removing them, locking and unlocking can each be taken back
with one undo — with every value it computed and every lock it made — a
removed record comes back as the very record, where it was; see
[Undo and redo](https://ui.publictheta.com/foundations/history.md) and
[Changes to records](https://ui.publictheta.com/foundations/record-changes.md).

**Change only what was changed.** An edit to a cell alters that row and no
other, so everything else that shows the data stays as it was. Where the rows
are too many to copy on each change, report an edit as what it changed: the
cells, the records added, the records removed.

**Hold together when the data changes.** Rows may arrive late, be replaced, or
grow fewer while the grid is in use. The selection stays within the rows there
are; an edit ends when the record it was editing is gone.

## Platform considerations

**Pointer and keyboard.** See [Grid navigation](https://ui.publictheta.com/foundations/grid-navigation.md) and
[Cell selection](https://ui.publictheta.com/foundations/grid-selection.md).

**Pointer.** A drag selects, and scrolls the grid when it passes an edge. A
secondary press selects the cell under it unless that cell is already selected
— and on a row's header, the row — so that the menu opened there acts on what it
appears over. The handle that fills, and a header's drag, are a mouse's.

**Touch.** Rows are at least 44 CSS pixels tall. A tap makes a cell active; a
tap on the active cell edits it. A cell holds no field until it is edited, so
that selecting one does not bring up the keyboard. A finger has no Shift and its
drag scrolls, so the selection has two handles, at its start corner and its end,
each at least 44 pixels across: dragging one redraws the selection from the
other, taking in the next row once the finger is half a row into it, and
scrolling the grid when the finger comes within reach of an edge — within it,
since a finger cannot always go beyond. A handle lies over the headers where its
corner is at their edge, so that the whole of it can be pressed, and is hidden
once its corner scrolls beneath them; tapped and not dragged, it is not in the
way of what is under it. A finger that rests on a cell selects it and opens the
menu, as does a tap on the selection anywhere but its active cell; on a phone
the menu is a sheet. There is no handle to fill by — it would lie under the
selection's own — and no dragging a header, which scrolls: filling, a series
too, is in the menu, and a finger that rests on a column's header opens the column's menu, to
sort, fit and move it.

**Right to left.** Columns run from the right, and the arrows, Home and End
follow them.

**Assistive technology.** The grid states its name, its numbers of rows and
columns, the place of each row and column as it is shown — whatever is in view,
however it is sorted and wherever its columns have been moved — which column it
is sorted by and which way, which cells are selected, and which cells hold what
is not allowed. A cell is described by its lock and by what it is computed as,
and a column's header by what it is computed as or by why its formula cannot be
used. Focus is on the active cell, or on the header reached from it.
The handles a pointer drags are not offered to it: the keyboard and the menus do
all they do. What cannot be seen happening — a paste, a fill, a column moved, an
undo, text that could not be read, a clipboard that could not be reached, a
change a lock refused — is said.

## Behaviour

Each statement is verified by a test in Chromium, Chromium (touch), Firefox, WebKit, WebKit (touch), unless it names the environments it applies to or is marked open.

### Semantics

- Is a grid with its name, counting every row and column, the header included.
- Its columns are the fields of the schema, named by their labels.
- Each cell shows its value as its kind is shown.
- Has no accessibility violations.

### Rows in view

- Of a hundred thousand rows, only those in view are drawn.
- Scrolling draws the rows that come into view, each stating its place among all.
- The active cell stays drawn when it is scrolled out of view.

### Rows that change

- A list too long for a browser to lay out is still scrolled, and moved, to its end.
- A grid whose rows arrive after it does has its first cell active once they do.
- When the row being edited goes away, the edit is over and the keys are the grid's again.
- A row that is missing shows as an empty one.

### Moving

- A click makes a cell the active one.
- Arrow keys move the active cell.
- The command key with an arrow, and Home and End, go to the edges.
- Moving to a cell out of view brings it into view.
- The grid is one stop in the Tab sequence: Tab enters at the active cell, and Tab again leaves.
- Focus is on the active cell's own control, and follows the active cell. *(Chromium, Firefox, WebKit)*

### Right to left

- Moving to a column out of view brings it into view.

### Selecting

- Shift with an arrow extends a rectangle from the active cell.
- Shift-click selects the rectangle from the active cell to the cell clicked.
- Command-click adds a cell, which becomes the active one. *(Chromium, Firefox, WebKit)*
- Pressing a cell and dragging to another selects the rectangle between them. *(Chromium, Firefox, WebKit)*
- A press on a column's header selects the column.
- Command-A selects every cell and leaves the active cell where it was.
- Escape leaves only the active cell selected.
- Dragging past the edge of the view scrolls it, selecting as it goes. *(Chromium, Firefox, WebKit)*
- A frame that comes late scrolls as far as its lateness, to a limit: a slow machine scrolls no slower. *(Chromium, Firefox, WebKit)*
- Shift with Space selects the active cell's row, and Control with Space its column.
- A secondary press outside the selection selects the cell it lands on; within it, the selection is kept. *(Chromium, Firefox, WebKit)*
- With Shift, a press on a column's header selects from the active cell's column to it.
- A press on a checkbox with Shift or the command key selects, and leaves the box as it was. *(Chromium, Firefox, WebKit)*

### Editing

- Enter opens the active cell for editing, with the caret at the end of its text.
- Typing begins editing, and what was typed replaces the cell's text.
- Enter commits and moves down; Tab commits and moves right.
- What is typed is read as the column's kind reads it.
- Escape abandons the edit and leaves the cell as it was.
- Text that cannot be read keeps the cell in editing, marked invalid.
- A double click begins editing.
- A choice opens its list, and choosing ends the edit.
- Space turns a yes-or-no cell on and off, without editing.
- Delete clears every selected cell.
- An edit replaces the row it is in, and no other.
- A grid that is given no way to change its rows cannot be edited.
- Begun by typing, an edit ends with the arrow that moves on: the value is not stepped.
- Opened for editing, the arrows move within the text; F2 goes from the one way to the other.
- Shift with Tab at the start of a row goes to the end of the row above.
- After Tabs across a row, Enter goes to the next row in the column the Tabs began in.
- In text of several lines, Enter commits, and Alt with Enter makes a new line.
- An edit that cannot be read is not left by Tab, by an arrow, or by a press elsewhere; it says why.
- Leaving the grid commits what was typed; what cannot be read stays as typed, marked.
- Escape pressed to an input method that is composing does not abandon the edit.
- The first key of an input method's composition begins editing, and composes in the cell. *(Chromium)*
- A key held down opens one cell, not each in turn.
- With the command key, Enter gives what was typed to every selected cell.
- Typing on a choice chooses the option that starts with what was typed.
- Escape closes a choice's list, and the keys are the grid's again.
- A cell holding what its field does not allow is marked, and says why.

### Adding and removing records

- After the last record is a row that is not one yet: typing in it makes a record, and another row waits after it.
- The row that is not yet a record is named as one to its header, and nothing in it is marked as missing.
- A grid that cannot be given records has no such row.
- The arrows step on into that row; the command key with an arrow, and selecting a column or everything, stop at the last record.
- Nothing is not worth a record: clearing that row, or typing in it and taking the typing back, makes none.
- A press on a row's header selects the row; with Shift, the rows from the active cell's to it.
- The corner where the headers meet selects every cell.
- With the command key, Backspace removes the rows that are selected whole, and says so.
- Rows of which only some cells are selected are not removed.
- Removing rows is undone by putting back the very records, where they were.
- A record made by typing is taken away again by one undo.

### Undoing

- The command key with Z undoes the last change; with Shift, or with Y, it is done again.
- One undo takes back everything one action did, and selects the cells it restored.
- With nothing to undo, nothing happens.
- Rows replaced by their owner are other rows: what was done to the old ones is not undone in them.

### Clipboard

- Copying puts the selected cells on the clipboard as a spreadsheet would, as they are shown. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Pasting a block fills cells from the active cell, each read as its column's kind. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Pasting one cell fills every selected cell with it. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Pasted text that is not a value of the column's kind leaves that cell as it was. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- A block that reaches past the last column is cut off there; past the last record, it makes records to hold it. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Where records cannot be added, a block is cut off at the last one. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Cutting copies the cells and clears them. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- With every cell selected, cutting copies every cell: what is cleared is what was copied. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- With cells selected in separate places, cutting takes the active range and leaves the others. *(Chromium, WebKit)*
- A clipboard that holds no text changes nothing.
- A block is repeated over a selection it divides evenly. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- A paste says how many cells it filled, and how many it left because their text could not be read. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Copying offers the cells as a table too, for what reads one. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*

### The menu

- Shift with F10, and the Menu key, open a menu of what can be done with the selection, against the active cell.
- Closing the menu returns focus to the active cell, and the keys are the grid's again.
- A secondary click opens it at the pointer, for the cell it lands on. *(Chromium, Firefox, WebKit)*
- A secondary click on a row's header selects the row, unless it is selected already, and opens the menu for it. *(Chromium, Firefox, WebKit)*
- Clear empties the selected cells, and Delete row removes every row the selection is in.
- Insert row above puts an empty record in above the selection — as many as the rows it is in — and says so; one undo takes them out.
- Copy puts the selection on the clipboard as text and as a table, and says how many cells.
- Cut clears what it copied once it has been copied; where the clipboard cannot be written, nothing is cleared and that is said.
- Paste reads the clipboard where the browser permits, and pastes as the keyboard's paste does.
- Where the browser does not permit the clipboard to be read, Paste changes nothing and says so.
- What cannot be done is there and cannot be chosen: in a grid that only shows, everything but Copy.
- With only read-only columns selected, nothing that would change them can be chosen.
- While a cell is being edited, the menu asked for is its field's own.
- Has no accessibility violations when open.

### Filling

- Under a mouse, the selection has a small handle at its end corner. *(Chromium, Firefox, WebKit)*
- Dragging the handle down fills the cells it passes over by repeating the selection, as one change that one undo takes back. *(Chromium, Firefox, WebKit)*
- Numbers a constant step apart are continued; one number, or text, is repeated. *(Chromium, Firefox, WebKit)*
- Dates a constant number of days apart are continued. *(Chromium, Firefox, WebKit)*
- Dragged upward, the handle fills back from the selection's start. *(Chromium, Firefox, WebKit)*
- The cells a drag would fill are outlined as it goes, along one axis only; Escape takes the drag back. *(Chromium, Firefox, WebKit)*
- A handle pressed and let go without being dragged out of the selection fills nothing. *(Chromium, Firefox, WebKit)*
- Dragged to the side, it fills along the columns: a series through columns of its kind. *(Chromium, Firefox, WebKit)*
- A read-only cell in the way of a fill is left as it was, and that is said. *(Chromium, Firefox, WebKit)*
- Between columns of different kinds a value goes as its text; what the column cannot read is left, and that is said. *(Chromium, Firefox, WebKit)*
- A fill dragged into the row that is not yet a record makes one. *(Chromium, Firefox, WebKit)*
- A grid that only shows has no handle to fill by. *(Chromium, Firefox, WebKit)*
- The command key with D fills the selection down from its first row, and with R across from its first column.
- With one row selected, filling down takes the row above; in the first row there is nothing to take.
- The menu's Fill series continues the series that the values at the selection's start make, through the rest of it: under a finger, where there is no handle, too.
- The menu fills down and across too, for a pointer with no handle to drag.

### Touch

- A tap makes a cell active, and a tap on the active cell edits it. *(Chromium (touch), WebKit (touch))*
- Under a finger, a cell holds no field until it is edited, so that selecting it brings up no keyboard. *(Chromium (touch), WebKit (touch))*
- On a touchscreen, every row is at least 44 pixels tall. *(Chromium (touch), WebKit (touch))*
- Under a finger, the selection has a handle at its start corner and one at its end, each at least 44 pixels across. *(Chromium (touch), WebKit (touch))*
- Dragging the end handle extends the selection from the active cell; dragging the start handle redraws it from its end, which becomes the active cell. *(Chromium (touch), WebKit (touch))*
- A finger that rests on a handle before dragging it still drags it, and opens no menu. *(Chromium (touch), WebKit (touch))*
- A handle takes in the next row once the finger is half a row into it, and not before. *(Chromium (touch), WebKit (touch))*
- A handle dragged to within reach of the grid's edge scrolls the grid, selecting as it goes. *(Chromium (touch), WebKit (touch))*
- A mouse lying over the grid does not end a finger's drag: only the pointer that began it does. *(Chromium (touch), WebKit (touch))*
- A handle taken where it already is within reach of an edge does not scroll the grid until it is dragged on towards the edge. *(Chromium (touch), WebKit (touch))*
- A handle that is tapped, not dragged, is not in the way of the cell beneath it. *(Chromium (touch), WebKit (touch))*
- A handle at the edge of the cells lies over the headers, where the whole of it can be pressed; tapped there, it is not in the way of the header. *(Chromium (touch), WebKit (touch))*
- A handle whose corner is scrolled out from the cells in view is not shown over the headers. *(Chromium (touch), WebKit (touch))*
- A finger that rests on a cell selects it and opens the menu for it; what is chosen there is done, and focus is the cell's again. *(Chromium (touch), WebKit (touch))*
- A tap on the selection, but for its active cell, opens the menu and leaves the selection as it is. *(Chromium (touch), WebKit (touch))*

### API: locks

- `locks` makes the locks the owner's: the grid shows them, and asks through `onLocksChange` for a change, heard before the change it came with.
- `messages` gives the words for locks and computed values in another language.

### Column widths

- Each column has an edge that says what it resizes, and how wide the column is.
- Dragging a column's edge makes the column wider or narrower by as far as it is dragged. *(Chromium, Firefox, WebKit)*
- A column is made no narrower than can be pressed, however far its edge is dragged. *(Chromium, Firefox, WebKit)*
- Dragging an edge selects nothing, and a press on it is not a press on the header. *(Chromium, Firefox, WebKit)*
- Pressing an edge twice fits the column to its name and to what its cells hold.
- Alt and Shift with an arrow to the side move the edge of the active cell's column.
- An edge that has focus is moved by the arrows to the side.
- Right to left, an edge dragged leftward widens its column, and the arrows are mirrored.
- Moving to a column brings it into view, whatever the widths of those before it.

### Column headers

- The up arrow from a column's first cell goes to the column's header, and the down arrow comes back.
- Only the plain up arrow leaves the cells: with Shift it extends nothing, and with the command key it stays at the first row.
- Along the headers, the arrows to the side, Home and End go from header to header.
- Enter or Space on a header sorts by its column, and focus stays on the header.
- On a header, Alt and Shift with an arrow to the side move its column's edge, and with an arrow up or down sort by it.
- Tab from a header goes on from the grid, which is one stop; Escape goes back to the active cell.
- The Menu key, or Shift with F10, on a header opens the column's menu: sorting either way, fitting, and moving.
- Fit to contents, in the column's menu, fits the column to its name and to what its cells hold.
- A secondary click on a column's header opens the column's menu, and leaves the selection as it is. *(Chromium, Firefox, WebKit)*
- A finger that rests on a column's header opens the column's menu. *(Chromium (touch), WebKit (touch))*
- A grid with no rows still lets the keyboard reach its headers: Tab reaches the grid, an arrow its first header, and Escape comes back.
- Every header and cell states its column's place, and every row its place as it is shown, whatever is in view and however it is sorted.

### Moving columns

- A header dragged past the middle of its neighbour takes its column there, and not before. *(Chromium, Firefox, WebKit)*
- Dragging a header selects nothing, and pressing one without dragging still selects its column. *(Chromium, Firefox, WebKit)*
- The active cell and the selection go with their columns. *(Chromium, Firefox, WebKit)*
- While a header is dragged a line shows where its column would land; Escape takes the drag back. *(Chromium, Firefox, WebKit)*
- Where the system forces its colours, the header being dragged and the line where it would land take the highlight. *(Chromium)*
- On a header, Alt with an arrow to the side moves the column that way; focus goes with it, and where it now is is said.
- Right to left, a column moves the way its arrow points.
- A header held at the edge of the view scrolls the columns to it, and its column lands where it is let go. *(Chromium, Firefox, WebKit)*
- However long a frame takes, the columns scroll as far in the same time: a slow frame scrolls further. *(Chromium, Firefox, WebKit)*
- Right to left, a header dragged leftward moves onward. *(Chromium, Firefox, WebKit)*
- The column's menu moves it before the column before it, or after the one after; the first column has none before.
- What was done before a column was moved is still undone in the cell it was done to.

### Columns that are only shown

- A cell of a field that is read-only says so, and typing, Enter and a double click do not edit it.
- Clearing, pasting and a click on its checkbox pass over such a column, and change the others.

### Sorting

- A column's header has a button that sorts by it: ascending, then descending, then not at all.
- Sorting shows the records in another order, and leaves them as they are kept.
- An edit made while sorted changes the record that is shown there.
- A row does not move when the value it is sorted by is changed, until it is sorted again.
- Sorting keeps the active cell on its record.
- Alt and Shift with the up arrow sort by the active cell's column ascending, with the down arrow descending, and the same again not at all.
- A record with nothing in the column sorted by comes last, either way.
- A record typed into the last row stays there, whatever it holds.
- Removing rows while sorted removes the records shown in them, and undo puts them back where they were shown.
- Rows replaced by their owner are sorted afresh.
- Copying while sorted copies the rows as they are shown.

### Computed and locked cells

- A computed column follows what it is computed from: an edit goes out with every value it moves, as one change.
- A computed column's header says it is computed, and as what.
- A computed cell is marked, and says what it is computed as, which assistive technology hears with it.
- Typing over a computed cell freezes it at what was typed: it is marked and says so, and no longer follows.
- A held cell cannot be changed, is marked, and says why — when typed at, aloud.
- An edit that would move a held value is not made: the cell keeps what was typed, marked, with why, said aloud; Escape puts it aside.
- Enter asks for a refused edit again, which a lock taken off since lets through.
- A paste over locked cells does what it may and leaves the rest, and says once how many were refused and by what. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- Clearing and filling over locked cells leave them in the same way, and say so.
- A computed cell written with what it would come to anyway is not frozen: a row pasted whole goes on following. *(Chromium, Chromium (touch), WebKit, WebKit (touch))*
- One undo takes back an edit with everything it computed and the lock it made; redo does them again.
- The cells' menu has the lock of a computed cell: Hold value and Freeze; for a frozen one, Hold value and Unfreeze; for a held one, Unlock.
- Where the owner does not work with locks, only a computed cell's menu has them; where it does, every cell's.
- A finger that rests on a computed cell opens the menu with its lock. *(Chromium (touch), WebKit (touch))*
- Freeze keeps a computed cell as it is; Unfreeze computes it again, as one change, and one undo freezes it again.
- On a range, the menu holds, freezes or unlocks every cell it applies to, and one undo takes it back.
- Unfreezing that would move a held value is refused, and says so.
- Locks go with their records when the rows are sorted, records are put in and records are taken out.
- Records known by a key of their own keep their locks whatever their owner does to the rows.
- A record made in the row that waits computes what it computes; typed over there, a computed cell is frozen in it.
- Rows are shown as they are given: what they compute is made right where an edit reaches it.
- A formula that cannot be used is said on its column's header, which is marked, and its cells are not computed.
- Has no accessibility violations with computed, frozen, held and refused cells.

### Columns: reordering and filtering by kind


## Reference implementation

React: `npx shadcn@latest add https://ui.publictheta.com/r/data-grid.json`
