Component
Editable cell
A table value that is edited where it is shown, with saving and error states.
Plain-text source, for agents and clipboard use: /components/source/editable-cell.txt
When to use it
Lets someone correct many values without leaving the table: a rate matrix, allocations by date, prices by room type. The value is a button until it is activated, then an input; Enter saves, Escape cancels, and the cell shows that it is saving and, if the save fails, why, with the value still there to fix.
Examples
Rate matrix
Each rate is one editable cell. Without a flux-edit listener a value saves at once; a listener calls event.detail.waitUntil(request) to show saving, and rejects with an Error to show its message.
| Room | 12 Sep | 13 Sep | 14 Sep | 15 Sep |
|---|---|---|---|---|
| Double | ||||
| Twin | ||||
| Single |
<div class="flux-table-wrap">
<table class="flux-table flux-table--compact">
<caption>Hotel Vík, rates per night in ISK. Select a rate to change it.</caption>
<thead>
<tr>
<th scope="col">Room</th>
<th scope="col" data-numeric>12 Sep</th>
<th scope="col" data-numeric>13 Sep</th>
<th scope="col" data-numeric>14 Sep</th>
<th scope="col" data-numeric>15 Sep</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Double</th>
<td class="flux-editable" data-flux-editable="Double, 12 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Double, 12 Sep: </span><span class="flux-editable__text">32,000</span></button></td>
<td class="flux-editable" data-flux-editable="Double, 13 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Double, 13 Sep: </span><span class="flux-editable__text">32,000</span></button></td>
<td class="flux-editable" data-flux-editable="Double, 14 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Double, 14 Sep: </span><span class="flux-editable__text">34,500</span></button></td>
<td class="flux-editable" data-flux-editable="Double, 15 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Double, 15 Sep: </span><span class="flux-editable__text">34,500</span></button></td>
</tr>
<tr>
<th scope="row">Twin</th>
<td class="flux-editable" data-flux-editable="Twin, 12 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Twin, 12 Sep: </span><span class="flux-editable__text">30,500</span></button></td>
<td class="flux-editable" data-flux-editable="Twin, 13 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Twin, 13 Sep: </span><span class="flux-editable__text">30,500</span></button></td>
<td class="flux-editable" data-flux-editable="Twin, 14 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Twin, 14 Sep: </span><span class="flux-editable__text">33,000</span></button></td>
<td class="flux-editable" data-flux-editable="Twin, 15 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Twin, 15 Sep: </span><span class="flux-editable__text">33,000</span></button></td>
</tr>
<tr>
<th scope="row">Single</th>
<td class="flux-editable" data-flux-editable="Single, 12 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Single, 12 Sep: </span><span class="flux-editable__text">24,000</span></button></td>
<td class="flux-editable" data-flux-editable="Single, 13 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Single, 13 Sep: </span><span class="flux-editable__text">24,000</span></button></td>
<td class="flux-editable" data-flux-editable="Single, 14 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Single, 14 Sep: </span><span class="flux-editable__text">26,000</span></button></td>
<td class="flux-editable" data-flux-editable="Single, 15 Sep" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Single, 15 Sep: </span><span class="flux-editable__text">26,000</span></button></td>
</tr>
</tbody>
</table>
</div>States
View, editing, saving and error, rendered as the script leaves them.
| State | Double, 12 Sep |
|---|---|
| View | |
| Editing | |
| Saving | Saving |
| Error | Below the contracted minimum of 28,000. |
<div class="flux-table-wrap">
<table class="flux-table flux-table--compact">
<thead>
<tr><th scope="col">State</th><th scope="col" data-numeric>Double, 12 Sep</th></tr>
</thead>
<tbody>
<tr>
<th scope="row">View</th>
<td class="flux-editable" data-numeric data-state="view"><button class="flux-editable__value" type="button"><span class="flux-visually-hidden">Edit Double, 12 Sep: </span><span class="flux-editable__text">32,000</span></button></td>
</tr>
<tr>
<th scope="row">Editing</th>
<td class="flux-editable" data-numeric data-state="editing"><input class="flux-input flux-editable__input" type="text" inputmode="decimal" value="34,000" aria-label="Double, 12 Sep" /></td>
</tr>
<tr>
<th scope="row">Saving</th>
<td class="flux-editable" data-numeric data-state="saving"><button class="flux-editable__value" type="button" disabled><span class="flux-visually-hidden">Edit Double, 12 Sep: </span><span class="flux-editable__text">34,000</span></button><span class="flux-loading flux-editable__status" role="status"><span class="flux-spinner flux-spinner--sm" aria-hidden="true"></span><span class="flux-visually-hidden">Saving</span></span></td>
</tr>
<tr>
<th scope="row">Error</th>
<td class="flux-editable" data-numeric data-state="error"><input class="flux-input flux-editable__input" type="text" inputmode="decimal" value="3,400" aria-label="Double, 12 Sep" aria-invalid="true" aria-describedby="rate-error" /><p class="flux-field__help" data-tone="error" id="rate-error">Below the contracted minimum of 28,000.</p></td>
</tr>
</tbody>
</table>
</div>Anatomy
| Part | Description |
|---|---|
| Value | A button showing the value, with visually hidden text naming what it edits. Outlined in border-strong on hover. |
| Input | An Input sized to the cell, labelled with the cell’s row and column. |
| Saving | The new value in text-secondary with a small Spinner, announced as Saving. |
| Error | The input stays open with the attempted value, aria-invalid, and the reason below it. |
API
| Name | Kind | Description |
|---|---|---|
flux-editable | class | On the td. data-state is view, editing, saving or error. Add data-numeric for figures. |
data-flux-editable | attribute | The cell’s label, such as "Double, 12 Sep", used to name the input. Wires editing. Requires flux-components.js. |
flux-editable__value | class | The button. Holds a flux-visually-hidden "Edit <label>: " span and the flux-editable__text span. |
flux-editable__text | class | The displayed value. The script reads and writes it. |
flux-editable__input | class | Added with flux-input while editing. |
flux-edit event | element | Cancelable, dispatched on the cell on save with detail.value, detail.previous and detail.waitUntil(promise). Cancel it to keep the field open. |
States
| State | Treatment |
|---|---|
| View | The value, outlined in border-strong on hover and focus. |
| Editing | An input in place, with its text selected. |
| Saving | The new value in text-secondary, the button disabled, a small Spinner at the start of the cell. |
| Error | The input reopens with the attempted value, the reason below it, and focus in the field. |
Keyboard
| Keys | Action |
|---|---|
Enter / Space | On a value, starts editing. |
Enter | In the input, saves and returns focus to the value. |
Escape | Cancels and returns focus to the value, unchanged. |
Tab | Saves and moves to the next cell. |
Accessibility
- Each value is a button whose name says what it edits and what it holds: "Edit Double, 12 Sep: 32,000".
- The input is labelled with the cell’s row and column, so it makes sense on its own.
- Saving is announced through a status element; an error is bound to the input with aria-describedby.
- Keep row and column headers with scope, so a screen reader moving through the grid hears both.
Tokens
The tier-3 and tier-2 tokens this component binds to. Change the token, not the component.
- --flux-editable-padding-x | -radius | -border-hover | -saving-text
- All --flux-table-* tokens, for the cells.
Do and don’t
Do
- Validate on the server and return a message that says what is allowed: "Below the contracted minimum of 28,000."
- Keep the attempted value in the field when a save fails, so it can be corrected rather than retyped.
- Format figures in the cell the same way before and after editing.
Don’t
- Do not save on every keystroke.
- Do not show an input in every cell at once. A grid of inputs reads as a form and loses the row rhythm.
- Do not mark editable cells with a colour fill. The hover outline and the button are enough.