CRUD Views
This page shows the five CRUD views in the default theme: list, create, read, update, and destroy. It also shows PageTitle, the layout header that each view fills. Components describes the rules that every component page shares.
PageTitle
PageTitle is the page header that the layout places once above the routed view. It shows the active view's title as the page heading, a loading indicator beside the title, and the page actions at the right. The sticky prop pins the header to the top of the viewport. Place the Page Title and Page Actions describes how a layout and a view connect to it.
The header keeps its height stable. Space for the loading indicator is always reserved, and a skeleton holds the title's place while a view has registered but has no title yet. Long action lists wrap onto a second line.
In the demos below, a docs wrapper stands in for the layout.
Theme keys: PageTitle. PageTitle.title sets the heading type, and PageTitle.buttons lays out the actions.
Sticky action bars
The create, read, and update views put their submit, action, and transition buttons in a StickyBar that joins the layout's sticky stack below the title. Sticky Chrome describes the bar and when it appears, and Place the Page Title and Page Actions describes the layout side. The docs harness has no sticky stack, so each bar in the demos renders in place.
ViewList
ViewList is the entry point for a model. From top to bottom it shows:
- the page title, with a button per list-level action (such as Create) in the title row;
- an under-actions bar with the Filters and Sort triggers and the search box;
- a constraints band with the active scope, filter, and sort chips, shown only while one is active;
- an ObjectsGrid with the records;
- a bulk-actions strip while rows are selected;
- a pagination footer.
The columns come from the model config's displayFields (see View Field Lists). By default that is every field except the pk, hidden fields, and fields that the model info leaves out of the list. Each cell renders through a Column Adapter; Customize List Column Rendering lists them. The list saves the reader's page size, sort, filters, and hidden columns in the browser for each app and model; Configure CRUD Views describes these list preferences.
The demo below is the live component against 28 offline records. Every control works.
A wider model
This demo is the same component against a twelve-column model with a value of every shape: dates, a datetime, two ranges, a duration, a multi-value choice, a JSON blob, file and image paths, and an IP address. Every nullable column is empty in at least one row, and the last row is empty in all of them.
The demo sets tableBreakpoint to xs to force table mode. With the lg default, the grid shows cards below that breakpoint; ObjectsGrid describes both layouts.
A wide table scrolls sideways inside its own card. Date, time, datetime, boolean, duration, and JSON fields have their own adapters (ColumnDateTime, ColumnBoolean, ColumnDuration, and ColumnJson). Range and multi-value choice fields fall back to ColumnText, which prints an object as compact JSON, so a range shows its lower and upper keys.
ViewCreate
ViewCreate shows the page title, a sticky bar with the Create button, and a form built from the model config. The form uses the create view's displayFields, which default to every writable field except the pk and hidden fields. A form-level error appears above the fields. A save that returns warnings opens a Warning Confirmation dialog.
After a successful create, the view opens the new record's update view. The redirectAfter prop picks list, update, or read.
ViewRead
ViewRead shows one record as read-only label and value rows, through a FormModel in the read view. Each field renders through its read-only widget, which is WidgetReadOnly by default. Boolean, date, datetime, time, duration, file, image, and JSON fields have type-specific read-only widgets (utils/fieldMappings).
A heading above the fields names the record by its Formatted Name. The heading scrolls with the fields and is not part of the sticky header, so a long name wraps. A skeleton holds its place while the record loads. A model whose serializer leaves out formatted_name gets no heading. The record-name slot replaces the heading.
The sticky bar holds the record's detail actions and its transitions:
- One LinkModelView button for each configured detail action that the record's Available Actions list includes, except the read action itself.
- One button for each of the record's Valid Transitions.
Update renders as the filled primary button, and the primaryActions prop picks which actions render that way. Delete and destroy actions take the destructive tone. Actions that are not detail actions, such as List and Create, go to the page title.
ViewUpdate
ViewUpdate shows the create form filled from the fetched record, under the same record name heading as ViewRead. The record-name slot replaces the heading. Its sticky bar holds the Update button, the record's other detail actions, and its valid transitions. An unsaved-changes marker shows while any field differs from its original value.
If the record's available actions lack update, the view hides the Update button and shows an "Editing unavailable" notice in place of the form. The update-unavailable slot replaces the notice.
After a successful save the view stays on the page unless redirectAfter is list or read. Configure CRUD Views describes which fields the view fetches and submits.
ViewDestroy
ViewDestroy is a routed page, so a user can link to it, bookmark it, or reach it from a multi-step flow. It renders a ModelActionForm as a page in the danger tone. The sticky action bar holds the Delete button, and the page body below it has three parts:
- A banner states what the delete removes. The
linkedObjectCountsprop adds one line per related model that the delete also removes. - A list shows each selected record, fetched by pk, as its Formatted Name. A record without one shows its pk.
- An optional type-to-confirm phrase, set by
confirmText, keeps the confirm button disabled until the reader types it.
The page title reads "Delete" followed by the model's verbose name. A bulk destroy reads "Delete", the record count, and the plural verbose name. The confirm button reads "Delete" and takes the destructive tone. The page title holds a Go Back button, so the page has no separate cancel button.
Customization surface
These tokens apply across the five views:
- --card and --background set the view card and the page background.
- --border sets dividers, separators, and card edges.
- --primary tints filter chips, the unsaved-changes marker, and selected rows.
- --destructive colors the destroy view's banner icon, selected-records panel, and confirm button, and every delete or destroy action button.
- --muted and --muted-foreground set the constraints band tint and secondary text such as read-view labels.
Some parts of a finished screen are additions to the default views:
ViewReadrenders one flat form. Grouping fields into titled sections is a customization.PageTitlerenders the title text. A status badge beside it goes in thetitleslot.- Action and transition button labels are the action name in title case. To change one on
ViewRead, use theaction-button,targetless-action-button, andtransition-buttonslots; each replaces one button and receives its label. ViewDestroypasses its slots through to its form. Theconfirm-buttonandcancel-buttonslots replace either button.
Theme keys by area:
- PageTitle:
root,title, andbuttons. - Sticky bars: Sticky Chrome.
- The grid: ObjectsGrid, ObjectsGridTableHeader, and ObjectsGridBodyCell, described on ObjectsGrid.
- Fields: Field, FieldLabel, and FieldContent, described on Forms.
- Alert: the form-level error banner.
- Button: the destructive tone styles the delete and destroy buttons in action bars and the destroy view's confirm button. Buttons describes the tones.