Apex Grid Styles and Themes

The Apex grid styles itself out of the box. As of v3 there is no theme stylesheet to import and no configureTheme() call: the grid renders through a set of --ag-* CSS custom properties that resolve to self-contained defaults. A grid mounted with zero consumer CSS already looks complete; you re-skin it by overriding tokens.

Coming from v2's per-framework theme imports? See the v2 → v3 migration guide.

Rebranding with CSS variables

Every visual decision the grid makes resolves to an --ag-* custom property. Override them on apex-grid itself, or on any ancestor element: custom properties inherit across the shadow boundary, so a value set on a wrapper cascades into every internal component (rows, cells, header, toolbar, pager). That includes the shadow host of a web component the grid renders in, however deep, so a theme set once on your own host element reaches every grid inside it.

Before 3.6 only a value set on the grid element itself took effect: the grid declared every default on its own host, and a declaration on an element beats an inherited value.

apex-grid {
  --ag-brand: #7c3aed;   /* selection, focus rings, checkboxes, active sort */
  --ag-radius: 12px;     /* outer card radius */
  --ag-row-h: 40px;      /* row height */
  --ag-font: 'Inter', system-ui, sans-serif;
}

Overriding the ~12 brand and surface tokens is enough to re-skin the entire grid. The brand-derived washes (--ag-brand-soft, --ag-brand-line, …) are mixed from --ag-brand with color-mix(), so a one-line brand change cascades to every tint automatically.

Dark mode

A grid with theme="dark" reads its colours from the --ag-dark-* inputs instead of the light ones, so a light palette set on a shared ancestor never puts a white surface under the dark theme's light text. Set the dark values beside the light ones, on the same element:

.app {
  --ag-surface: #ffffff;
  --ag-text: #111827;

  --ag-dark-surface: #0f172a;
  --ag-dark-text: #f8fafc;
  --ag-dark-text-body: #e2e8f0;
  --ag-dark-hairline: rgba(255, 255, 255, 0.12);
}

There is a dark input for every colour theme="dark" changes: --ag-dark-grid-bg, --ag-dark-surface, --ag-dark-surface-elevated, --ag-dark-text, --ag-dark-text-body, --ag-dark-text-muted, --ag-dark-text-subtle, --ag-dark-hairline, --ag-dark-border, --ag-dark-pin-line, --ag-dark-header-separator, --ag-dark-shadow-card, and the good, watch, risk and gold state colours (--ag-dark-good-text, --ag-dark-good-soft, and so on). Sizing, typography, motion, the brand and the base state colours are shared by both modes.

From 3.6 a light token such as --ag-surface set above a dark grid, or on the dark grid element itself, no longer applies to it. Move those values to their --ag-dark-* names.

A grid that stays on the light theme can still be recoloured for a dark page by swapping the light tokens under a class or media query.

Token reference

The defaults shown below sit on :where(:root), at zero specificity, so a value you set on any element wins. The grid adopts them into the document when the first grid connects, through adoptedStyleSheets (no <style> element, so no CSP nonce is needed), which also makes var(--ag-surface) readable from your own CSS anywhere on the page. The brand tokens additionally re-tint from an Ignite UI palette if your app happens to define one (see Ignite UI auto-tint).

Two kinds of token have no root default, so page CSS that reads them needs its own fallback, for example var(--ag-brand, #0d6fd3):

  • --ag-brand and --ag-brand-strong, so that an Ignite UI palette set on any ancestor keeps tinting the grid.
  • The derived tokens (--ag-brand-soft, --ag-brand-softer, --ag-brand-line, --ag-row-hover, --ag-surface-alt). They are mixed on the grid host, so they follow a brand set lower down, and they still accept an override; the defaults in the tables below are what they mix to.

Without the adopted stylesheet

setup({ tokenStyles: false }) skips adopting the defaults into the document. The grid renders the same, because it carries the same values as fallbacks; what you lose is reading var(--ag-*) from your own CSS. To keep that, or when rendering on the server, load the defaults as a plain stylesheet:

import 'apex-grid/tokens.css'

Brand

TokenDefaultPurpose
--ag-brand#0d6fd3Primary blue: selection, focus, checkboxes, active sort
--ag-brand-strong#0a56b8Hover / pressed brand state
--ag-brand-softbrand @ 10%Selected-row / chip wash
--ag-brand-softerbrand @ 7%Chip / control wash
--ag-brand-linebrand @ 18%Chip / search borders

Neutral chrome & surfaces

TokenDefaultPurpose
--ag-hairlinergba(15,23,42,.08)Header / structural gridlines
--ag-row-hoverneutral washRow hover background (opaque)
--ag-surface#fffGrid card background
--ag-surface-alt#f9fafbAlternating row tint
--ag-surface-elevatedsubtle gradientHeader background
--ag-borderrgba(15,23,42,.05)Row separators

Sticky regions (header, footer, pinned columns) paint background: inherit, so the surface tokens must stay opaque: a translucent surface lets scrolled content bleed through.

Text

TokenDefaultPurpose
--ag-text#111827Primary text
--ag-text-body#1f2937Row text
--ag-text-muted#6b7280Roles, labels
--ag-text-subtle#9ca3afPlaceholder, ellipsis
--ag-text-on-brand#fffText on brand-filled surfaces

State

TokenDefaultPurpose
--ag-good / --ag-good-text / --ag-good-softgreenPositive / healthy state
--ag-watch / --ag-watch-text / --ag-watch-softamberWarning / trial state
--ag-risk / --ag-risk-text / --ag-risk-softredNegative / churn state
--ag-gold / --ag-gold-line / --ag-gold-textgoldAccent pill (e.g. Enterprise badge)
--ag-shadow-cardsoft floating shadowReusable elevated-card shadow (see below)

Typography

TokenDefaultPurpose
--ag-fontInter stackGrid font family
--ag-fs-cell12pxCell text size
--ag-fs-cell-secondary10.5pxRole line / header labels
--ag-fs-tiny10pxTags, page ellipsis
--ag-fw-regular / --ag-fw-semibold / --ag-fw-bold500 / 600 / 700Font weights
--ag-tracking-tight / --ag-tracking-header-0.005em / 0.05emLetter spacing
--ag-leading1.4Line height

Spacing & sizing

TokenDefaultPurpose
--ag-cell-pad-x10pxHorizontal cell padding
--ag-row-h / --ag-header-h36pxRow / header height
--ag-toolbar-pad / --ag-footer-padnoneToolbar / footer padding
--ag-radius18pxOuter card radius
--ag-radius-sm / --ag-radius-xs / --ag-radius-pill6px / 4px / 20pxChips, checkboxes, badges

Motion

TokenDefaultPurpose
--ag-easecubic-bezier(.2,.8,.2,1)Shared easing curve
--ag-dur-fast / --ag-dur-med / --ag-dur-slow120ms / 180ms / 240msHover, chevron, fill transitions

Grid shadow / edge

The grid host shows a flat 1px hairline edge by default, with no floating-card shadow. The look is controlled by a dedicated override hook, --ag-grid-shadow. This is an override hook, not a default token (it is not part of the token list above):

apex-grid { --ag-grid-shadow: var(--ag-shadow-card); }      /* restore the elevated card */
apex-grid[theme="dark"] { --ag-grid-shadow: var(--ag-dark-shadow-card); } /* the same, on a dark grid */
apex-grid { --ag-grid-shadow: none; }                       /* remove the edge entirely */

--ag-shadow-card is the reusable soft floating shadow defined in the token surface; assigning it to --ag-grid-shadow reproduces the elevated card look from earlier versions.

The tinted theme

theme="tinted" mixes the brand into the surface, gridline and muted-text values in effect, yours included if you set them, instead of replacing them (from 3.6). Its row separators are the brand alone, so a tinted grid ignores --ag-border and --ag-dark-border. The default tinted look is unchanged.

Styling parts

Every part a row renders can be styled with apex-grid::part(): the cell, and from 3.6 also the selection, expansion, detail panel, row number and reorder parts, and the parts of rows a module renders (enterprise group headers, total rows and master-detail panels). Before 3.6 only cell was reachable, so existing ::part() rules for those names now take effect. Parts inside your own detailTemplate content are re-exported when listed in expansion.exportParts.

Ignite UI auto-tint

The grid does not depend on Ignite UI and never did anything with it beyond one pass-through call, which is why the dependency was removed in 3.5.0. The tinting hooks survived that removal, because they are nothing more than CSS fallbacks: --ag-brand resolves to var(--ig-primary-500, #0d6fd3) and --ag-brand-strong to var(--ig-primary-700, …).

So if your page defines --ig-primary-* (by loading any Ignite UI base theme: Bootstrap, Material, Fluent, Indigo, light or dark), the grid picks the palette up with no configuration, and a grid on a page that does not keeps the self-contained premium blue. You do not have to install anything for this, and you never did: any stylesheet that sets --ig-primary-500 will drive it.

If you are not in an Ignite UI app, set --ag-brand directly instead. That is what the Themes demo below does.

Removed in 3.5.0: the --igx-* custom-property level. Every colour rule used to resolve through three levels and the first never fired, because --igx-* is the naming convention of a different package. --ag-* overrides and the unprefixed semantic variables behave exactly as before.

Live examples

  • Custom Styling: light/dark theme built entirely from --ag-* overrides.
  • Themes: four brand presets in light and dark, each built from --ag-brand plus the tinted theme flag, with no external CSS.