Flux 2.0

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.

Not this
Not for values that depend on each other and must be saved together; use a form with one Save. Not for long text. Not for the only copy of a value that needs review before it changes; open a Modal with the field and its consequences.

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.

Hotel Vík, rates per night in ISK. Select a rate to change it.
Room 12 Sep 13 Sep 14 Sep 15 Sep
Double
Twin
Single
html
<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.

StateDouble, 12 Sep
View
Editing
Saving Saving
Error

Below the contracted minimum of 28,000.

html
<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

PartDescription
ValueA button showing the value, with visually hidden text naming what it edits. Outlined in border-strong on hover.
InputAn Input sized to the cell, labelled with the cell’s row and column.
SavingThe new value in text-secondary with a small Spinner, announced as Saving.
ErrorThe input stays open with the attempted value, aria-invalid, and the reason below it.

API

NameKindDescription
flux-editableclassOn the td. data-state is view, editing, saving or error. Add data-numeric for figures.
data-flux-editableattributeThe cell’s label, such as "Double, 12 Sep", used to name the input. Wires editing. Requires flux-components.js.
flux-editable__valueclassThe button. Holds a flux-visually-hidden "Edit <label>: " span and the flux-editable__text span.
flux-editable__textclassThe displayed value. The script reads and writes it.
flux-editable__inputclassAdded with flux-input while editing.
flux-edit eventelementCancelable, dispatched on the cell on save with detail.value, detail.previous and detail.waitUntil(promise). Cancel it to keep the field open.

States

StateTreatment
ViewThe value, outlined in border-strong on hover and focus.
EditingAn input in place, with its text selected.
SavingThe new value in text-secondary, the button disabled, a small Spinner at the start of the cell.
ErrorThe input reopens with the attempted value, the reason below it, and focus in the field.

Keyboard

KeysAction
Enter / SpaceOn a value, starts editing.
EnterIn the input, saves and returns focus to the value.
EscapeCancels and returns focus to the value, unchanged.
TabSaves 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.