Every colour, space, radius and font of a grid comes from a --masu-* custom property. Set them on .masu (or higher) to theme the grids:

.masu {
  --masu-accent: #0f766e;
  --masu-font-size: 0.8125rem;
}

The defaults have no specificity, so a value set on :root, on an ancestor or on .masu itself wins. They are Kotoba's and Koyomi's own defaults, so that a page with all three looks like one thing.

The properties

PropertyIs
--masu-font, --masu-font-size, --masu-line-heightthe type
--masu-text, --masu-mutedtext, and empty values
--masu-background, --masu-surfacethe rows, and the surfaces
--masu-border, --masu-rulethe frame, and the lines between rows
--masu-grid-border, --masu-grid-radius, --masu-grid-shadowthe grid's box (a 1px --masu-border with --masu-radius unless set; none and 0 for a table without one)
--masu-header-background, --masu-header-textthe header
--masu-header-heightthe header's row (the rows' height unless set)
--masu-header-font-size, --masu-header-font-weight, --masu-header-text-transform, --masu-header-letter-spacingthe header's type
--masu-header-paddinga header cell's padding (the cells' unless set)
--masu-header-border, --masu-header-shadowthe line under the header, or its shadow
--masu-header-hovera header cell under the pointer
--masu-row-background, --masu-row-alt-backgroundthe rows, and every second row (zebra rows: the second, the fourth, as :nth-child(even) counts)
--masu-row-borderthe line under each row (a 1px line of --masu-rule unless set; none gives a row its whole height)
--masu-row-hover, --masu-cell-hovera row and a cell under the pointer, over the row's own background
--masu-selection, --masu-activea selected range, and the line of the active cell
--masu-accent, --masu-accent-text, --masu-focus-ringthe focus
--masu-loadingthe bars of a row that is coming
--masu-invalidthe line of a cell the server refused
--masu-avatar-sizethe avatars of the others (Collaboration)
--masu-radius, --masu-cell-padding, --masu-space, --masu-field-heightshape and space
--masu-transitionthe speed of the small transitions

A changed row has the color of the person who saved it in --masu-flash (the accent unless someone saved it). The grid sets --masu-cols, --masu-width and --masu-row-height on itself: the widths of the columns and the height of a row (the row_height attribute).

Density

density sets the room around a cell's text: :compact (rows of 28 pixels), :normal (36) or :comfortable (48), and their padding. row_height sets the rows' height itself.

<.data_grid id="orders" rows={@orders} density={:compact}>

Dark mode

Masu follows the app's own switch: a dark class on an ancestor (as Tailwind and Phoenix's generated apps put on <html>), or data-theme="dark". It does not follow the system's setting by itself, so that a light app stays light; an app that does maps its own media query to one of them.

Its dark palette is a neutral one. Set your own under the same switch:

.dark .masu {
  --masu-text: #ececef;
  --masu-background: #17171a;
  --masu-surface: #1f1f23;
  --masu-border: #33333a;
  --masu-rule: #26262b;
}

With Tailwind

Map the tokens to your theme's colors in app.css, once, for light and dark:

/* Tailwind 4 */
.masu {
  --masu-font: var(--font-sans);
  --masu-text: var(--color-zinc-800);
  --masu-background: var(--color-white);
  --masu-header-background: var(--color-white);
  --masu-row-alt-background: var(--color-zinc-50);
  --masu-accent: var(--color-indigo-600);
}

.dark .masu {
  --masu-text: var(--color-zinc-100);
  --masu-background: var(--color-zinc-900);
  --masu-header-background: var(--color-zinc-900);
  --masu-row-alt-background: var(--color-zinc-800);
}

With Tailwind 3, use theme(): --masu-text: theme(colors.zinc.800). class on the grid takes any class of yours, to scope a look to some grids: <.data_grid class="report-grid" ...>.

Make Masu look like your table

An app with tables of its own wants its grids to look like them. Measure your table (a browser's computed styles of a header cell, a cell, an even row), and set the tokens to the values. Lotus Web's results table, in tokens, with row_height={52}:

.lotus-results {
  --masu-font: "Inter var", sans-serif;
  --masu-font-size: 0.875rem;
  --masu-line-height: 1.25rem;
  --masu-text: rgb(39 39 42);
  --masu-background: rgb(255 255 255);
  --masu-header-background: rgb(255 255 255);
  --masu-header-text: rgb(39 39 42);
  --masu-header-height: 48px;
  --masu-header-border: none;
  --masu-header-shadow: 0 1px 0 0 rgb(209 213 219);
  --masu-header-hover: rgb(239 246 255);
  --masu-grid-border: none;
  --masu-grid-radius: 0;
  --masu-row-border: none;
  --masu-row-alt-background: rgb(249 250 251);
  --masu-row-hover: transparent;
  --masu-cell-hover: rgb(239 246 255);
  --masu-cell-padding: 0 0.75rem;
}

.dark .lotus-results {
  --masu-text: rgb(244 244 245);
  --masu-background: rgb(23 23 23);
  --masu-header-background: rgb(23 23 23);
  --masu-header-text: rgb(244 244 245);
  --masu-header-shadow: 0 1px 0 0 rgb(75 85 99);
  --masu-header-hover: rgb(30 58 138 / 0.2);
  --masu-row-alt-background: rgb(55 65 81);
  --masu-cell-hover: rgb(30 58 138 / 0.2);
}

In Lotus Web itself, the values would be its own variables (rgb(var(--lotus-gray-50))), so that its themes apply. Masu's e2e suite renders Lotus Web's table, with Lotus Web's own CSS, next to a grid with these tokens, and compares them pixel for pixel, light and dark, at rest and with a cell under the pointer (e2e/specs/look.spec.ts; the dev server's /look).

The Sumi theme

masu-sumi.css maps every property to the Sumi tokens, as kotoba-sumi.css and koyomi-sumi.css do. Load it after masu.css:

@import "../../deps/masu/priv/static/masu.css";
@import "../../deps/masu/priv/static/masu-sumi.css";

On a page with the Sumi themes, the grids follow them. Without them, the built-in Sumi palette follows data-theme ("dark" or "light"), then prefers-color-scheme. The selection is a faint wash of the blade, and the active cell a line of it.

Classes

ClassIs
.masuthe grid's element
.masu-density-compact, .masu-density-comfortablea grid's density
.masu-row-evenevery second row (the second, the fourth), by its place in the query
.masu-toolbar, .masu-searchthe search box
.masu-toolbar-actions, .masu-toolbar-button, .masu-columns-button, .masu-views-buttonthe toolbar's buttons (see Layout and views)
.masu-pinned, .masu-pinned-start, .masu-pinned-end, .masu-pinned-edgea cell of a pinned column, and of the last (or first) one, which draws the edge; it sticks at --masu-pin-<n>
.masu-moving, .masu-dragged, .masu-dropa column being moved: the grid, its header, and the line where it goes
.masu-panel, .masu-columns-panel, .masu-columns-item, .masu-columns-pin, .masu-columns-movethe columns menu
.masu-views-panel, .masu-views-item, .masu-views-apply, .masu-views-shared, .masu-views-delete, .masu-views-formthe views menu
.masu-gridthe scroll box, with the grid role
.masu-head, .masu-header, .masu-hcellthe header
.masu-sortable, .masu-sort, .masu-resizera header that sorts, its mark, and its edge
.masu-body, .masu-row, .masu-cellthe rows and cells
.masu-align-start, -center, -end, .masu-type-<type>a cell's alignment and type
.masu-active, .masu-selected, .masu-col-selectedthe active cell, a selected cell, a header of a selected column
.masu-loading, .masu-null, .masu-emptya row that is coming, an empty value, the grid with no rows
.masu-busy, .masu-resizinga query being applied, a column being resized
.masu-filter-buttonthe funnel of a header; [data-active] when its column is filtered
.masu-menu, .masu-menu-op, .masu-menu-input, .masu-menu-value, .masu-menu-count, .masu-menu-applythe filter menu
.masu-chips, .masu-chip, .masu-chip-text, .masu-chip-remove, .masu-chips-clearthe filters above the grid
.masu-editable, .masu-editor, .masu-saving, .masu-invalida cell that edits, its editor, a cell being saved, a cell refused
.masu-choices, .masu-choicean enum's editor: the list of its values, and each
.masu-pager, .masu-pager-range, .masu-pager-previous, .masu-pager-next, .masu-pager-formthe pager of a grid of pages
.masu-add-row, .masu-draftthe row "+ New row", and a new row not saved yet
.masu-confirm, .masu-confirm-ok, .masu-confirm-cancel, .masu-dangera question above the grid (delete rows, add the rest of a paste), and an answer that deletes
.masu-lockeda cell that the row's state makes read-only, in a column that edits
.masu-people, .masu-person, .masu-initials, .masu-people-morethe avatars of the others; each has --masu-peer-color
.masu-peer, .masu-peer-label, .masu-peer-editing, .masu-peer-topanother person's cell, their name, while they type, on the first row; with --masu-peer-color
.masu-peer-rangeanother person's selected range, a row at a time; with --masu-peer-color
.masu[data-following], .masu-following, .masu-following-stopfollowing a person: the grid (with --masu-follow-color; its .masu-chips take the color), the banner (data-kind: following, follow_hidden, their_view), its button; the avatar has aria-pressed="true"
.masu-content, .masu-details, .masu-detailthe header and rows (the browser's), the open details (the server's), a detail
.masu-tree-cell, .masu-twisty, .masu-detail-togglethe first cell of a tree's row (indented by --masu-level × --masu-indent), its arrow (data-state: open, closed, leaf), a detail's arrow (data-open)
.masu-group-row, .masu-group-label, .masu-group-counta group's row, its label, its count
.masu-foot, .masu-totalsthe row of totals at the bottom
.masu-chip-group, .masu-menu-groupthe chip of a grouped column, the Group by button of the header menu
.masu-select, .masu-select-all, .masu-row-chosen, .masu-selection-bar, .masu-selection-clear, .masu-selection-deletea row's box, the header's box, a row selected, the bar of how many, and its Delete
.masu-export-button, .masu-export-panel, .masu-export-formatthe Export menu (aria-busy on its button while it asks for the link)
.masu-changeda row that changed on the grid's topic, for a moment (see Live rows)