Marigold
v18.1.0
Marigold
v18.1.0

Layout

App Framealpha
Admin- and Mastermarkbeta

User Input

Bulk Actionsbeta
Filterbeta
Formsbeta
Multiple Selection
Pickalpha
Table Recordsalpha

Feedback

Async Data Loading
Destructive Actionsalpha
Error Boundariesbeta
Feedback Messages
Loading States

Data

Fetching and Mutationsalpha
Patterns

Table Records

Help users create, correct, and maintain records in data heavy tables without losing their context.

In data heavy applications the table is where the work happens. A user scans it and fixes a stale status, adds a record a colleague requested, or types in a whole list from a spreadsheet. Each is a different task with different needs, though every one ends with a changed table.

This pattern starts from those tasks. For each, it describes the user's situation, the problems that come with it, and the interaction that solves them with the <Table> and <Drawer> components. It also covers the moments where the table's own features, such as filters and sorting, work against the user. The focus is the interaction shell around a record, meaning how the user opens, fills, and saves it.

For building the forms themselves, see the Forms pattern. For narrowing which records are shown, see the Filter pattern. For acting on many records at once instead of one at a time, see the Bulk Actions pattern.

Match the approach to the task

The right interaction follows from two questions:

  1. How much input does the task need?
  2. How much of the table does the user still need to see while doing it?

A quick correction needs almost no room but full view of its row. A new record needs a complete form, yet still benefits from the table as reference. Only rarely does a task need so much space that giving up the table is worth it.

SituationUseWhy
The user spots one wrong value, such as a stale status, a typo in a name, or an off quantityInline edit (<Table.EditableCell>)Zero context switch. The user stays in the table. Best for high frequency, low complexity corrections.
The user fills in or revises the same few fields across many rows in one sitting, such as prices or allocationsData-entry grid (a <Table> of form controls)The table is the form. Every row stays visible for comparison while typing, and the user never leaves it. Moving between fields costs three keys, so this is only for bulk, repetitive entry across a bounded set of rows. See Filling in many rows at once.
The user creates a record or revises several fields of an existing oneDrawer (<Drawer> and a form)The default. It preserves the table as reference through a split screen model, handles hidden columns gracefully, and supports progressive disclosure for longer forms.
The editing task is stepped, nested, or needs its own URL, such as a multi step wizard or a record with editable sub tablesFull page (a dedicated route)For tasks whose shape outgrows a panel, not for records that merely have many fields. It removes the table from view, so reserve it for tasks that do not lean on the table as reference. See When a full page is the right choice.

The drawer is the default for anything beyond a single field, with one exception. When the task is to fill in the same few fields across many rows, a data-entry grid fits the shape of the work better than reopening a form per row. Reach for a full page when the task is stepped, nested, or needs its own URL, and accept that it trades away the table as reference to gain space and focus.

Do not use a dialog for record creation or editing

A dialog blocks the table behind it, which is exactly the context people rely on during data entry. Dialogs also create nested scrolling once a form grows. Keep dialogs for confirmations, such as asking before a delete, and use a drawer for record creation and editing. To select a subset of records rather than edit them, see the Pick pattern.

The three that keep the table in view all change the same records, and the difference between them is the shape of the task rather than the amount of data. Try each below on the same starting data, where you can correct a single value, fill in every row, or add records one after another. Each tab keeps its own copy, so switching tabs starts over.

The same table, three ways

Inline edit
Data-entry grid
Drawer
Reach for this when one value is wrong and the user spotted it while scanning. Editing opens in a popover, so only the cell being corrected is in play.
Venue
Capacity
Price
Main Street Amphitheater
500
€34.50
Shakytown Comedy Club
300
€22.00
Harbor Lights Hall
850
€41.00
The Old Tannery
220
€18.50
Northgate Arena
4,200
€55.00
Riverside Pavilion
640
€29.00

Fixing a single value

A user scanning the table spots a wrong capacity, a misspelled name, or a stale status. The change takes a second. If the interface answers with a full form in a drawer or on a separate page, the cost is out of all proportion to the change, and the user loses their place in the table. Corrections like this happen often, so even a small detour adds up. Inline editing removes the detour by letting the user correct the value in the cell itself, without ever leaving the table.

Use <Table.EditableCell> for the columns that should be editable, and leave the rest as plain cells. The field prop holds the input that appears while editing, and the children hold the value shown at rest. On save, read the submitted value from the form data and update your record.

Venues

Select a cell to edit its value in place.

Name
City
Capacity
Status
Main Street Amphitheater
Laughville
500
Active
Shakytown Comedy Club
Shakytown
300
Active
Harbor Lights Hall
Portbury
850
Draft
The Old Tannery
Riverside
220
Archived
Northgate Arena
Northgate
4,200
Active

A few rules keep inline editing safe and predictable:

  • Reserve it for single, low risk fields. A status, a name, or a quantity is a good fit. Anything with complex validation, foreign key relationships, or cascading side effects belongs in a drawer.
  • Validate inline. Keep the field constraints on the input itself, for example a required name, so an invalid value is caught before it commits and the user sees the message in place.

Creating and editing whole records

Creating a record or revising several fields needs a real form, which raises the question of what happens to the table meanwhile. While filling a form, people keep referring back to it. They check how a similar record was filled in, copy a spelling, or compare a capacity against the row above. An interaction that hides or blocks the table removes the reference material that data entry depends on.

A drawer solves this with a split screen model. It slides in from the side and holds the full form while the table stays visible and scrollable behind it. The user can read another record, copy a value across, and come back to the form without losing their place in either.

The same drawer handles both jobs. An "Add" button opens it with an empty form for creation. An "Edit" button on each row opens it prefilled with that record for editing. The title and the submit behavior follow the mode, but the form layout is identical, so users learn one screen instead of two.

Venues

Add a venue or edit an existing one.

Name
City
Type
Capacity
Status
Actions
Main Street Amphitheater
Laughville
Outdoor venue
500
Active
Shakytown Comedy Club
Shakytown
Club or lounge
300
Active
Harbor Lights Hall
Portbury
Formal venue
850
Draft

Keep the experience consistent with these rules:

  • Use a medium drawer for most forms (size="medium"). It gives the form enough room while leaving the table readable. Move to a wider size only when the fields genuinely need it.
  • Show all relevant fields regardless of column visibility. The form is independent of which columns the table currently shows (see When the table hides form fields).
  • Help the user keep their bearings while editing. The table stays visible behind the drawer as a reference. Where your table supports it, highlighting the matching row is a nice touch, but it is a recommendation rather than a requirement.
  • Confirm before discarding unsaved changes. Allow dismissal through the close button, but warn the user if the form has edits that would be lost.

Layout inside the drawer

Build the form with the same building blocks as any other Marigold form. Wrap the fields in a <Form>, use <Drawer.Title> for the heading, <Drawer.Content> for the fields, and <Drawer.Actions> for the buttons. See the Forms pattern for field order, spacing, and the action hierarchy.

Entering records in a batch

Sometimes a record does not come alone. A user works through a list from an email, a stack of paper forms, or last season's data, entering one after another in a single sitting. Two sources of friction now repeat with every record. The drawer closes after each save and must be reopened, and values shared across the batch must be retyped.

A second action in the drawer, "Save and add another", removes the first friction. It commits the current record and immediately resets the form for the next one without closing the drawer. This turns record creation into a fast, repeatable loop.

Venues

Name
City
Type
Capacity

No venues yet

Add your first venue to start building the list.

The loop only feels good when the details are handled with care:

  • Keep "Save" as the primary action. Style "Save and add another" as secondary so the two do not compete for visual weight.
  • Confirm each save without blocking. The new row appearing in the table behind the drawer is the clearest signal the record landed. A short toast can reinforce it, but keep it clear of the drawer's actions so it never covers the buttons during a rapid loop.
  • Return focus to the first field. After the form resets, move focus back to the top so the user can start typing the next record straight away.
  • Retain the fields that repeat. Carry contextual values into the next entry and clear only the record specific fields, as described below.

Retaining values that repeat

When a batch is related, keep the contextual fields that stay constant, such as city, category, or date, filled between entries, and clear only the record specific fields like name and capacity. When in doubt, clear everything, because a blank field is safer than a wrong default.

Filling in many rows at once

The batch drawer above exists for creating records. Each one is a whole record with more fields than the table shows, so it needs a form, and the loop exists to reopen that form quickly.

A different task looks similar and is not. The rows already exist, and the user has to set one or two values on each of them. Ticket prices for the coming season, seat allocations per venue, a stocktake. Here the drawer's strength becomes its cost. The user opens a form to change one number, closes it, opens the next, and the interaction spends more effort on navigation than on the work. What they actually want is to type a value, move to the next one, and keep comparing against the rows above as they go.

When the task is fill in this table rather than create these records, put the form controls in the cells and let the table be the form.

Seat allocation

Press the left or right arrow inside a field and the caret moves through the value instead of jumping to another cell. Shift+Tab steps back out onto the cell, where the arrow keys move between cells.

Venue
Capacity
Price
Note
Main Street Amphitheater
Shakytown Comedy Club
Harbor Lights Hall
The Old Tannery
Northgate Arena
Riverside Pavilion
Total
6,710
No changes

Set keyboardNavigationBehavior to tab

Form controls in cells need keyboardNavigationBehavior="tab" on the <Table>. Without it the grid keeps the arrow keys, so pressing one inside a field moves focus to the row instead of moving the caret.

Moving between two fields is a grid movement rather than a single Tab press, so every field the user visits costs a few keystrokes and the work grows faster than the table looks. That cost is what sets the limits.

  • Five editable columns at most, and only simple controls such as text, number, select, and switch. A date range, a lookup that opens its own popover, or a field whose validity depends on another field belongs in a drawer.
  • A bounded set of rows, roughly twenty five in view. Never put a data-entry grid behind infinite scroll or over an unbounded dataset.
  • Keep everything the user does not edit read-only. Identifiers, computed values, and totals stay plain cells, which also keeps them out of the tab order.
  • Commit the whole grid with one Save, and show how many rows are pending. Do not save per cell, because that is inline editing's model, and running both at once teaches two contradictory rules about when a change becomes real.
  • Do not mix always-on inputs and <Table.EditableCell> in the same table. Neither one breaks the other, so this is a consistency rule rather than a technical limit: the user ends up with two different ways to edit depending on which column they are in.
  • Validate in the field, and summarise on submit. Show the message on the offending input, and when Save is pressed with errors outstanding, name them together in a <SectionMessage>. Never trap focus in an invalid field, because a user who cannot Tab out of a mistake cannot reach the fix.
  • Add row selection only if bulk actions are genuinely part of the task. It works, but it costs another tab stop on every row.

Do

Use a data-entry grid for a bounded set of rows and a handful of simple fields, where the user's value comes from comparing rows while typing.

Don't

Don't turn a wide table into a grid of inputs. Eight editable columns across fifteen rows is a hundred and twenty fields to step through three keys at a time, and heavy controls like date pickers in every row crowd the row past reading. Use a drawer.

Notes on accessibility

Keep the identifying column as a plain read-only cell and mark it rowHeader, so the row stays scannable and assistive technology can announce which row a field belongs to. Give every input a name that includes the row, such as Price for Northgate Arena. A column header alone is not enough, because it leaves a screen reader user hearing "Price" once per row with nothing to tell them apart.

Setting the prop moves Tab in and out of a cell, not from one cell to the next.

KeyFromDoes
Taba focused cellsteps into the field in that cell
Shift + Taba focused fieldsteps back out onto its cell
arrow keysa focused cellmove to another cell
arrow keysa focused fieldmove the caret inside the value

So filling a column downwards is Shift + Tab, ↓, Tab per row, and pressing Tab inside a field leaves the table altogether rather than reaching the next one. The ARIA authoring practices model a grid as a single tab stop, which is why no table can give the field to field tabbing a form does, and why the limits above exist.

This is also why the choice between a grid and inline editing is not only a matter of taste. <Table.EditableCell> opens its editor in a popover outside the grid, so the arrow keys were never in contention and it needs no such setting. Always-on inputs live inside the cells, which is what puts them in the grid's way.

When a full page is the right choice

The drawer is the default for creating and editing, and it stays the default even for records with many fields. A long form is a reason to give the drawer more room and to group optional fields behind progressive disclosure, not a reason to leave the table. The table is the reference people lean on while they work, so giving it up always has a cost.

Reach for a full page (a dedicated route) when the editing task has one of these shapes, not simply when the record is large:

  • The task runs across multiple steps. A sequence of dependent steps, a wizard, or tabbed sections work poorly inside a panel. A page gives each step room and a place for a progress indicator.
  • The record contains nested records. When the form holds its own table or sub records, such as an order with editable line items, it has outgrown the panel and needs the structure of a page with sub sections.
  • The task does not lean on the table. This is the clearest signal. If the user never compares against other rows while editing, the drawer's main advantage is already gone, and a focused page serves the task better.
  • The edit needs its own URL. When the work has to be bookmarked, shared with a colleague, resumed after a refresh, or linked from elsewhere, it needs an address that an overlay cannot give it.
  • The change carries heavy downstream impact. When a save cascades into dependent records or processes and deserves a deliberate review before it commits, a page gives that review room. A drawer can already guard against lost edits, so choose a page here only when the review itself needs the space.

If none of these apply, stay with the drawer. A long form on its own does not justify the move, because the field order and progressive disclosure in When the table hides form fields absorb it without sacrificing the table. The full page is a deliberate choice for a particular kind of task, not a fallback for forms that feel big.

When a saved record disappears

Filtering, sorting, and pagination all decide which records are in view, and a freshly saved record has no special standing in that decision. It can drop out of sight the moment it is created, which to the user is indistinguishable from a failed save. They try again, create a duplicate, and stop trusting the table. The situations below show where this happens and how to prevent it.

Hidden by an active filter. When a filter is active, for example "Status is Active", and the user creates a record that does not match it, for example a draft, the record is hidden the moment it is saved. This is the filter paradox.

Address it from two directions:

  1. Inherit the active filters by default. When the drawer opens for creation, prefill the form with values that match the current filters. If the table is filtered to "Status is Active", the new record defaults to "Active", so it stays in view after saving.
  2. Warn before the record disappears. If the user changes a prefilled value to one that no longer matches the active filters, surface an inline message near the field or at the foot of the form, for example "Based on your current filters, this record will not be visible after saving." A <SectionMessage> with the warning variant fits this job.

The demo below starts filtered to active venues. Opening the form inherits that filter, so a new venue defaults to active and stays in view. Switch the status to draft to see the warning appear, then switch the filter to "All statuses" to watch the hidden record come back.

Venues

Status
Name
City
Status
Main Street Amphitheater
Laughville
Active
Shakytown Comedy Club
Shakytown
Active

Two things to avoid. Do not let the record vanish silently, because that destroys trust. And do not clear all filters after a save, because that throws away the workspace the user set up. Temporarily exempting the new row from the filters (a ghost row) is another option, but it adds complexity and can confuse people who do not understand why a non matching record is showing, so it is not recommended as the default.

The same principle governs picking from a dialog. An in-dialog search or filter must never clear the selections a user has already staged. See the Pick pattern for how a picker keeps staged selections while the visible list narrows.

Displaced by sorting or pagination. If the table is sorted alphabetically and the user creates "Zebra Corp", the record belongs near the end of the data, perhaps many pages away. A user sitting on page one will not see it appear, and the save once again looks like it failed.

Confirm the save with a <Toast> such as "Zebra Corp created", and consider giving the toast an action that jumps to the record's position. Do not temporarily override the sort to force the new record to the top, because that hides the real sort state and confuses the next interaction. During a "Save and add another" loop, keep the toast brief so it does not interrupt the rhythm of entry.

When the table hides form fields

People hide table columns to focus on what matters to them, but the creation form still has to collect every field, including the ones behind hidden columns. A form that rigidly follows the database schema ignores the configuration the user just set up, while a form that only shows visible columns cannot collect a complete record.

Respect the user's configuration without losing fields:

  • Order visible fields first. Fields that map to currently visible columns belong at the top of the form, ideally in the same left to right order as the columns. The form then mirrors the table, which gives the user cognitive continuity.
  • Group hidden fields behind progressive disclosure. Fields that map to hidden columns go below, inside a collapsed <Accordion> section such as "Additional fields". This honors the user's intent while still surfacing every field.
  • Keep required fields visible. If a required field maps to a hidden column, leave it in the main section with its required indicator rather than burying it in the accordion. Only optional hidden fields belong there.

For the form layout and progressive disclosure details, see the Forms pattern.

Related

Bulk Actions

Select many records and act on all of them at once, from selection scope to partial-failure feedback.

Forms

Field order, spacing, validation, and submission for the forms inside the drawer.

Filter

Narrow the records on display and understand the filter paradox.

Table

The data display, with selection, sorting, and editable cells.
Last update: 24 days ago

Pick

Find and collect records from a collection, then commit them as a set.

Async Data Loading

Learn how to implement async data loading patterns in interactive components.

© Reservix GmbH — Marigold Design System
ImpressumDatenschutzGitHubRSS

On this page

Match the approach to the taskFixing a single valueCreating and editing whole recordsEntering records in a batchFilling in many rows at onceWhen a full page is the right choiceWhen a saved record disappearsWhen the table hides form fieldsRelated