How Themes Work¶
When a button on a portal page is teal, that colour came from somewhere. It may be the theme's primary colour, a variant the widget picked, or a value typed on the widget itself. This page explains where a widget's colour, font or padding actually comes from, so you know which tool to reach for when you want to change it. Theme Designer walks through the screens themselves.
A typical case: you want one call-to-action button to stand out, but the rest of the buttons should stay as they are. If you change the primary colour in the theme, every button changes. If you set the colour on that one widget, only it changes. Knowing the layers below tells you which one does what you mean.
The layers¶
Every styled value on a portal page is decided by a stack of layers. Each layer can set a value. The highest layer that sets one wins.
| Layer | Where you set it | What it affects |
|---|---|---|
| Template | Chosen once, when the theme is created | The starting values of everything below. Also what Reset goes back to |
| Theme: base tokens | Base tab of the theme designer | The shared colours, fonts, spacing and corner radii that every component is built from |
| Theme: default variant | Components tab, with Mod: default selected | Every widget of that kind, in every portal using the theme |
| Theme: named variant (a mod) | Components tab, with another mod selected | Only the widgets that pick that variant |
| Widget theme values | A widget's properties panel, Theme section | That one widget |
| Overrides | Written in Base → Overrides, picked per widget part in the Theme section | One part of that one widget |
| Custom Style | A widget's Custom Style section | That one widget, as plain inline CSS |
| Custom CSS | Other → Custom CSS in the theme designer | Anything, in every portal using the theme. Applied after all of the above |
The first four layers live in the theme and are shared. The next three live on the widget. Use the lowest layer that does the job. A brand colour belongs in the base tokens. A "compact" button belongs in a variant. Only a true one-off belongs in Custom Style.
Templates¶
Every new theme starts from a template, either Standard or Dark. The template's values are copied into the theme. There is no live link afterwards. A later change to a template does not reach themes already created from it, and a theme cannot switch to another template.
The template still matters in two places:
- Reset. Reset on a field, and Reset to Default in Theme Info, go back to the template's value. For a theme made from Dark, that is the dark value, not the standard one.
- New properties. When a product update adds a new property to a component, older themes have no value for it. The value is taken from the theme's template. Older themes keep rendering, and the new field shows up already filled in.
Base tokens¶
The Base tab defines tokens: named values such as the primary colour or the medium spacing, reused everywhere. Every component's default styling refers to them. A button's background is not a hex colour but item background. A card's corner is radius small. Change the token once and every component that uses it follows.
Each token is also a CSS variable. You can use these variables in overrides, Custom Style and Custom CSS:
| Group | Variables |
|---|---|
| Meaning colours | --primary-background, --primary-text, --primary-border, each with -subtle, -hover and -active shades. The same for --danger-*. --warning-* and --positive-* have -subtle only |
| Surfaces | --canvas-background / --canvas-text (the page), --subtle-* (a recessed band), --surface-* (a raised card), --overlay-* (a dialog or dropdown), --item-* (rows and buttons) with -alt, -hover and -active |
| Elevation | --elevation-1 to --elevation-4 |
| Spacing | --space-xs, --space-sm, --space-md, --space-lg, --space-xl, --space-xxl |
| Radius | --radius-xs to --radius-xxl |
| Typography | --font-family-base, --font-family-heading, --font-family-code; --font-size-xs to --font-size-xxl; --font-weight-light to --font-weight-extrabold; --line-height-xs to --line-height-xxl |
| Borders | --border-color, --border-width, --ring-color, --ring-width |
| Chart palette | --cx-palette-color-0 to --cx-palette-color-15, plus the hover, selected and disabled lighten/darken amounts |
Use tokens from the same group together. --danger-background goes with --danger-text. A surface's -background goes with its -text.
Components, parts and states¶
Each kind of widget is styled through a component in the Components tab. A component is split into parts: the pieces of the widget you can style separately. A button has a body and an icon. A nav list has the list, its group headers and its items. The Anatomy menu in the editor's header shows how the parts nest.
Every part has a fixed set of properties, the ones the widget's styling actually uses. You cannot add a property the component does not have. If a part shows no Font Size, it has no text of its own to size.
Parts that react to the reader also have states. Base is always there. Depending on the part, you may also see Hover, Active, Disabled, Selected or Current. The rules are simple:
- A state starts out inheriting every value from Base. Inherited values are shown greyed out, and the state tab counts the ones you have changed.
- Clearing a state value puts it back to inheriting its base. The theme remembers that you cleared it, so a template update will not fill it again.
- A Base value can never be empty, because the base has nothing to fall back to.
Which value applies¶
For a part in a given state, the theme looks for the state's value first, across all layers, and only then falls back to the base. Take a hovered button. The search order is: hover value on the widget, hover value in the theme, then base value on the widget, then base value in the theme.
This has one practical consequence. Setting Background on a widget's Base does not recolour its hover, because the theme's Hover background still wins. To change the hover as well, set it in the widget's Hover tab too. The hint under each field tells you which layer and state the current value comes from.
Widget theme values¶
The Theme section of a widget's properties panel shows the same parts, states and properties as the component editor. The difference is that every field starts empty. Each field shows, greyed out, the value it would replace: the theme's value for the widget's current variant. Only what you type is stored on the widget.
A widget's state fields inherit from the widget's own Base first, then from the theme, following the lookup order above. Values set on the widget stay in place when you change its variant, because they are stored separately from it.
Where to make a change¶
| You want to… | Use |
|---|---|
| Change the brand colour, the font, or how roomy the whole portal feels | Base tokens |
| Change how every button, card or table looks | The component's default variant |
| Offer a second look for some widgets of a kind | A named variant, picked on those widgets |
| Give several widgets the same small tweak that no theme field covers | An override, picked on their parts |
| Adjust one widget | Its Theme section. Use Custom Style for things the theme does not expose |
| Patch something the designer cannot reach at all | Custom CSS, sparingly |
Good to know¶
- Some controls open floating panels: a parameter's lookup list or date calendar, or a pagination's page-size list. On touch devices these panels are drawn outside the widget's own element. The theme handles this for parameter widgets, so a variant or widget-level value you set on a lookup field also styles its open list. Two cases show the theme's default styling only: the page-size dropdown of the Pagination widget, and lookups filled from a data query.