Theming

May 4, 2026 · View on GitHub

UI Grid uses Shadow DOM encapsulation. All visual customization flows through CSS custom properties and ::part() selectors.

The runtime styles now normalize theme resolution around the public --ui-grid-* token names again. Legacy --app-ui-grid-* aliases still work as a fallback, but consumer themes should target the public --ui-grid-* surface.

How It Works

The grid component renders inside a Shadow DOM boundary. Parent styles cannot leak in. Instead, the grid exposes two theming surfaces:

  1. CSS custom properties — set --ui-grid-* variables on any ancestor element. They pierce the shadow boundary. Legacy --app-ui-grid-* aliases remain supported as fallback input for older app themes.
  2. ::part() selectors — target specific structural elements by their part name for full CSS access.

All public styling tokens follow the --ui-grid-* naming pattern.

.my-app {
  --ui-grid-surface: #1e1b2e;
  --ui-grid-accent: #8b5cf6;
  --ui-grid-header-background: #2d2640;
  --ui-grid-cell-color: #e2e0f0;
  --ui-grid-border-color: rgba(139, 92, 246, 0.2);
  --ui-grid-row-hover: #322e4a;
}

If you already ship older --app-ui-grid-* theme tokens, they still work as a compatibility fallback. New consumer themes should use only --ui-grid-*.

CSS Custom Properties

Foundation

VariableDefaultControls
--ui-grid-surface#ffffffBase surface for the frame, controls, menus, and inputs
--ui-grid-border-color#d4d4d8Shared border color across frame, cells, controls, and menus
--ui-grid-header-background#f3f4f6Header and filter row background
--ui-grid-header-weight700Header label font weight
--ui-grid-cell-color#111827Primary text color for rows and controls
--ui-grid-muted-color#6b7280Secondary text color for helper copy and inactive controls
--ui-grid-row-odd#fcfcfdOdd striped row background
--ui-grid-row-even#f7f7f8Even striped row background
--ui-grid-row-hover#eef4ffRow hover background
--ui-grid-accent#2563ebAccent color for active states, focus rings, and highlighted controls
--ui-grid-group-background#eceff3Group header row background
--ui-grid-radius4pxBase corner radius reused by frame, cards, and inputs
--ui-grid-shadow0 10px 24px rgba(15, 23, 42, 0.08)Frame shadow

Status Pills

VariableDefaultControls
--ui-grid-status-active-bgrgba(22, 163, 74, 0.14)Active pill background
--ui-grid-status-active-color#166534Active pill text
--ui-grid-status-expansion-bgrgba(37, 99, 235, 0.14)Expansion pill background
--ui-grid-status-expansion-color#1d4ed8Expansion pill text
--ui-grid-status-enterprise-bgrgba(15, 118, 110, 0.14)Enterprise pill background
--ui-grid-status-enterprise-color#115e59Enterprise pill text
--ui-grid-status-pilot-bgrgba(234, 88, 12, 0.14)Pilot pill background
--ui-grid-status-pilot-color#c2410cPilot pill text
--ui-grid-status-pill-padding-block0.2remVertical pill padding
--ui-grid-status-pill-padding-inline0.55remHorizontal pill padding
--ui-grid-status-pill-radius999pxPill border radius
--ui-grid-status-pill-font-size0.85remPill font size
--ui-grid-status-pill-default-bgrgba(15, 23, 42, 0.07)Default pill background when no named state class is applied

Shell And Hero

VariableDefaultControls
--ui-grid-shell-gap1.5remGap between major shell sections
--ui-grid-hero-gap1.5remGap between hero content and actions
--ui-grid-hero-padding-block1remHero top and bottom padding
--ui-grid-eyebrow-margin-block-end0.5remSpace under the eyebrow label
--ui-grid-eyebrow-letter-spacing0.18emEyebrow tracking
--ui-grid-eyebrow-font-size0.72remEyebrow size
--ui-grid-hero-title-font-sizeclamp(1.4rem, 2vw, 2rem)Hero title size
--ui-grid-hero-title-line-height1.1Hero title line height
--ui-grid-hero-deck-max-width56chMax width for the intro paragraph
--ui-grid-hero-deck-margin-block-start0.75remSpace above the deck paragraph
--ui-grid-hero-actions-gap1remGap between hero actions
--ui-grid-action-radius999pxCTA button radius
--ui-grid-action-padding-block0.8remCTA button vertical padding
--ui-grid-action-padding-inline1.1remCTA button horizontal padding
--ui-grid-stats-card-min-width8remMinimum width for hero stats cards
--ui-grid-stats-card-padding-block0.85remStats card vertical padding
--ui-grid-stats-card-padding-inline1remStats card horizontal padding
--ui-grid-stats-card-value-font-size1.9remHero stats numeric size

Frame, Metrics, Toolbar, And Scrolling

VariableDefaultControls
--ui-grid-metrics-gap1remGap between metric cards
--ui-grid-metric-card-padding-block1remMetric card vertical padding
--ui-grid-metric-card-padding-inline1.1remMetric card horizontal padding
--ui-grid-metric-value-font-size1.3remMetric value size
--ui-grid-toolbar-gap1remGap inside the toolbar
--ui-grid-toolbar-padding-block1remToolbar vertical padding
--ui-grid-toolbar-padding-inline1.25remToolbar horizontal padding
--ui-grid-toolbar-inline-gap0.4remInline gap in toolbar text blocks
--ui-grid-overflow-xautoHorizontal overflow behavior for the single grid scroll container
--ui-grid-overflow-yautoVertical overflow behavior for the single grid scroll container

Header, Pinning, And Controls

VariableDefaultControls
--ui-grid-header-z-index3Sticky header and filter row stacking level
--ui-grid-pinned-cell-z-index2Sticky pinned cell stacking level
--ui-grid-pin-menu-open-z-index8Header cell stacking level when the inline pin chooser is open
--ui-grid-pin-menu-z-index20Pin chooser internal stacking level
--ui-grid-header-cell-gap0.75remGap between header label and action cluster
--ui-grid-header-cell-padding-block0.85remHeader cell vertical padding
--ui-grid-header-cell-padding-inline1remHeader cell horizontal padding
--ui-grid-header-cell-background-activecolor-mix(in srgb, var(--ui-grid-accent) 8%, var(--ui-grid-header-background))Background for an active sorted header cell
--ui-grid-pinned-divider-shadow-left2px 0 4px rgba(0, 0, 0, 0.06)Shadow on the trailing edge of left-pinned columns
--ui-grid-pinned-divider-shadow-right-2px 0 4px rgba(0, 0, 0, 0.06)Shadow on the leading edge of right-pinned columns
--ui-grid-pinned-divider-clip-leftinset(0 -4px 0 0)Clip path for the left pinned divider shadow
--ui-grid-pinned-divider-clip-rightinset(0 0 0 -4px)Clip path for the right pinned divider shadow
--ui-grid-header-actions-gap0.4remGap between sort, group, and pin actions
--ui-grid-control-size2remShared square size for header action chips
--ui-grid-control-radius999pxShared control radius
--ui-grid-control-icon-size1remIcon size for action chips and pin arrows
--ui-grid-control-backgroundvar(--ui-grid-surface)Base control background
--ui-grid-control-border-colorvar(--ui-grid-border-color)Base control border color
--ui-grid-control-colorvar(--ui-grid-muted-color)Base control icon and text color
--ui-grid-control-background-activecolor-mix(in srgb, var(--ui-grid-accent) 12%, var(--ui-grid-surface))Active control background
--ui-grid-control-border-color-activecolor-mix(in srgb, var(--ui-grid-accent) 35%, var(--ui-grid-border-color))Active control border
--ui-grid-control-color-activevar(--ui-grid-accent)Active control icon and text color
--ui-grid-pin-menu-gap0.25remGap between inline pin chooser arrows
--ui-grid-pin-menu-padding0.25remPadding inside the inline pin chooser
--ui-grid-pin-menu-radius999pxInline pin chooser radius
--ui-grid-pin-menu-shadow0 10px 24px color-mix(in srgb, var(--ui-grid-cell-color) 10%, transparent)Inline pin chooser shadow
--ui-grid-pin-menu-action-size1.75remSize of each left or right pin action
--ui-grid-pin-control-collapsed-size1pxFinal collapsed size of the pin button while the chooser opens
--ui-grid-pin-control-transition-duration160msDuration for the inline pin control animation
--ui-grid-pin-control-transition-easingcubic-bezier(0.22, 1, 0.36, 1)Easing curve for pin control motion
--ui-grid-pin-menu-scale-closed0.72Closed-state scale used before the chooser expands in place

Filters

VariableDefaultControls
--ui-grid-filter-cell-padding-block-start0.75remFilter row top padding
--ui-grid-filter-cell-padding-block-end1remFilter row bottom padding
--ui-grid-filter-cell-padding-inline1remFilter row horizontal padding
--ui-grid-filter-input-padding-block0.55remFilter input vertical padding
--ui-grid-filter-input-padding-inline0.7remFilter input horizontal padding
--ui-grid-filter-input-focus-outline2px solid color-mix(in srgb, var(--ui-grid-accent) 18%, transparent)Focus outline for filter inputs

Rows, Cells, And Editing

VariableDefaultControls
--ui-grid-body-cell-padding-block0.95remBody cell vertical padding
--ui-grid-body-cell-padding-inline1remBody cell horizontal padding
--ui-grid-cell-shell-gap0.55remGap between toggles and content inside a cell
--ui-grid-row-toggle-size1.55remTree and expandable toggle size
--ui-grid-row-toggle-radius999pxTree and expandable toggle radius
--ui-grid-toggle-icon-size1.1remGeneral toggle icon size
--ui-grid-group-disclosure-icon-size0.9remGroup row disclosure icon size
--ui-grid-pagination-icon-size1.2remPagination icon size
--ui-grid-expandable-row-padding-block1remExpanded row vertical padding
--ui-grid-expandable-row-padding-inline1.25remExpanded row horizontal padding
--ui-grid-expandable-row-backgroundcolor-mix(in srgb, var(--ui-grid-accent) 4%, var(--ui-grid-surface))Expanded row background
--ui-grid-cell-focus-backgroundcolor-mix(in srgb, var(--ui-grid-accent) 10%, var(--ui-grid-surface))Focused cell background
--ui-grid-cell-focus-ringinset 0 0 0 1px color-mix(in srgb, var(--ui-grid-accent) 28%, transparent)Focus ring for keyboard-focused cells
--ui-grid-cell-editing-backgroundcolor-mix(in srgb, var(--ui-grid-accent) 7%, var(--ui-grid-surface))Editing cell background
--ui-grid-cell-editor-border-colorcolor-mix(in srgb, var(--ui-grid-accent) 45%, var(--ui-grid-border-color))Editor border color
--ui-grid-cell-editor-radiuscalc(var(--ui-grid-radius) - 1px)Inline cell editor radius
--ui-grid-cell-editor-padding-block0.45remEditor vertical padding
--ui-grid-cell-editor-padding-inline0.55remEditor horizontal padding
--ui-grid-cell-editor-focus-outline2px solid color-mix(in srgb, var(--ui-grid-accent) 18%, transparent)Editor focus outline

Group Rows And Drag States

VariableDefaultControls
--ui-grid-group-row-gap0.9remGap inside group rows
--ui-grid-group-row-padding-block0.9remGroup row vertical padding
--ui-grid-group-row-padding-inline1remGroup row horizontal padding
--ui-grid-drag-preview-shadow0 12px 30px rgba(15, 23, 42, 0.16)Drag preview shadow for column reordering
--ui-grid-drag-preview-radius0.9remDrag preview radius
--ui-grid-drag-placeholder-opacity0.3Placeholder opacity during drag
--ui-grid-drag-placeholder-backgroundcolor-mix(in srgb, var(--ui-grid-accent) 9%, var(--ui-grid-header-background))Placeholder background during drag
--ui-grid-drag-placeholder-border1px dashed color-mix(in srgb, var(--ui-grid-accent) 45%, var(--ui-grid-border-color))Placeholder border during drag
--ui-grid-drag-placeholder-shadowinset 0 0 0 1px color-mix(in srgb, var(--ui-grid-accent) 18%, transparent)Placeholder shadow during drag

Empty State And Pagination

VariableDefaultControls
--ui-grid-empty-state-gap0.35remGap between empty-state lines
--ui-grid-empty-state-min-height16remMinimum empty-state height
--ui-grid-empty-state-padding2remEmpty-state padding
--ui-grid-pagination-gap1remGap in the pagination bar
--ui-grid-pagination-padding-block0.85remPagination bar vertical padding
--ui-grid-pagination-padding-inline1.25remPagination bar horizontal padding
--ui-grid-pagination-controls-gap0.75remGap between pagination controls
--ui-grid-pagination-size-gap0.4remGap between rows-per-page label and select
--ui-grid-pagination-button-padding-block0.45remPagination button vertical padding
--ui-grid-pagination-button-padding-inline0.8remPagination button horizontal padding
--ui-grid-pagination-select-padding-block0.35remPagination select vertical padding
--ui-grid-pagination-select-padding-inline0.55remPagination select horizontal padding

::part() Hooks

Part NameTarget Element
shellOutermost grid wrapper
heroTitle/toolbar area
grid-frameScrollable grid container
grid-toolbarToolbar with row count
headerHeader row container
header-cellIndividual header cell
filter-cellFilter input cell
body-cellData cell
group-rowGroup header row
expandable-rowExpanded detail row
paginationPagination footer
empty-stateEmpty state message

Example: Custom Brand Theme

.my-app {
  /* foundation */
  --ui-grid-surface: #1e1b2e;
  --ui-grid-border-color: rgba(139, 92, 246, 0.2);
  --ui-grid-header-background: #2d2640;
  --ui-grid-cell-color: #e2e0f0;
  --ui-grid-muted-color: #8b7fb0;
  --ui-grid-row-odd: #1e1b2e;
  --ui-grid-row-even: #252238;
  --ui-grid-row-hover: #322e4a;
  --ui-grid-accent: #8b5cf6;
  --ui-grid-group-background: #2d2640;

  /* structure */
  --ui-grid-radius: 16px;
  --ui-grid-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
  --ui-grid-toolbar-padding-inline: 1.5rem;
  --ui-grid-body-cell-padding-inline: 1.25rem;

  /* controls */
  --ui-grid-control-size: 2.15rem;
  --ui-grid-pin-control-transition-duration: 200ms;
  --ui-grid-pin-menu-action-size: 1.9rem;
}

/* Target specific parts for full CSS access */
.my-app app-ui-grid::part(header) {
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

Host Attribute Pattern

The demo app uses data-color-mode and data-visual-mode attributes on the root component to drive a 2×2 theme matrix (dark/light × studio/wireframe). Each combination defines a full set of semantic CSS tokens that cascade into the grid:

:host([data-color-mode='dark'][data-visual-mode='default']) {
  --grid-surface: #0b1824;
  --grid-header: #112434;
  --grid-text: #ebf5f9;
  --grid-accent: #67e8f9;
  /* ...then map to --ui-grid-* on the grid container */
}

You can use this same pattern in your own app to support multiple themes without JavaScript logic. If you already have legacy --app-ui-grid-* aliases in your codebase, you can leave them in place while standardizing new theme work on the public --ui-grid-* names.