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-brandand--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
| Token | Default | Purpose |
|---|---|---|
--ag-brand | #0d6fd3 | Primary blue: selection, focus, checkboxes, active sort |
--ag-brand-strong | #0a56b8 | Hover / pressed brand state |
--ag-brand-soft | brand @ 10% | Selected-row / chip wash |
--ag-brand-softer | brand @ 7% | Chip / control wash |
--ag-brand-line | brand @ 18% | Chip / search borders |
Neutral chrome & surfaces
| Token | Default | Purpose |
|---|---|---|
--ag-hairline | rgba(15,23,42,.08) | Header / structural gridlines |
--ag-row-hover | neutral wash | Row hover background (opaque) |
--ag-surface | #fff | Grid card background |
--ag-surface-alt | #f9fafb | Alternating row tint |
--ag-surface-elevated | subtle gradient | Header background |
--ag-border | rgba(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
| Token | Default | Purpose |
|---|---|---|
--ag-text | #111827 | Primary text |
--ag-text-body | #1f2937 | Row text |
--ag-text-muted | #6b7280 | Roles, labels |
--ag-text-subtle | #9ca3af | Placeholder, ellipsis |
--ag-text-on-brand | #fff | Text on brand-filled surfaces |
State
| Token | Default | Purpose |
|---|---|---|
--ag-good / --ag-good-text / --ag-good-soft | green | Positive / healthy state |
--ag-watch / --ag-watch-text / --ag-watch-soft | amber | Warning / trial state |
--ag-risk / --ag-risk-text / --ag-risk-soft | red | Negative / churn state |
--ag-gold / --ag-gold-line / --ag-gold-text | gold | Accent pill (e.g. Enterprise badge) |
--ag-shadow-card | soft floating shadow | Reusable elevated-card shadow (see below) |
Typography
| Token | Default | Purpose |
|---|---|---|
--ag-font | Inter stack | Grid font family |
--ag-fs-cell | 12px | Cell text size |
--ag-fs-cell-secondary | 10.5px | Role line / header labels |
--ag-fs-tiny | 10px | Tags, page ellipsis |
--ag-fw-regular / --ag-fw-semibold / --ag-fw-bold | 500 / 600 / 700 | Font weights |
--ag-tracking-tight / --ag-tracking-header | -0.005em / 0.05em | Letter spacing |
--ag-leading | 1.4 | Line height |
Spacing & sizing
| Token | Default | Purpose |
|---|---|---|
--ag-cell-pad-x | 10px | Horizontal cell padding |
--ag-row-h / --ag-header-h | 36px | Row / header height |
--ag-toolbar-pad / --ag-footer-pad | none | Toolbar / footer padding |
--ag-radius | 18px | Outer card radius |
--ag-radius-sm / --ag-radius-xs / --ag-radius-pill | 6px / 4px / 20px | Chips, checkboxes, badges |
Motion
| Token | Default | Purpose |
|---|---|---|
--ag-ease | cubic-bezier(.2,.8,.2,1) | Shared easing curve |
--ag-dur-fast / --ag-dur-med / --ag-dur-slow | 120ms / 180ms / 240ms | Hover, 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-brandplus thetintedtheme flag, with no external CSS.