An explanation of how Tecton uses CSS variables to style components, and how to approach styling changes in different scenarios.
Tecton exposes an elaborate system of CSS variables, enabling you to greatly customize the look and feel of your product
How do you change the color of a button? Just set the background color using CSS. Sounds simple enough. But in Tecton the answer can be the result of a surprisingly complex calculation.
The styling of Tecton components is the result of cascading layers of variables that begin in the platform and work their way down to the individual components, whether in the platform itself or in an extension. This layer cake of styles often results in confusion about how and where best to change components for the desired effect.
In this guide, we'll walk through how Tecton implements variables, how those variables get set in a very complex environment using UUX as an example, and some strategies for how to approach changes to styling variables depending on different use cases.
Tecton sets many component CSS properties (e.g., padding, color, etc) using a stack of CSS variables. In order to isolate the intent and purpose of each variable in the stack, we've introduced a number of prefixes that are used to easily identify what they do and who should make changes to them.
Let's take a look at these first, to clear up any confusion before we move forward.
--const: ConstantsChange With Caution
These are fixed variables whose values should never change. At the time of writing, we only have five of these.
--app: Application GenericsTheme maintainers may change with caution in the theme files
These variables are used for what we'll call generic properties. Editing them should be done with caution as any modifications could result in considerable side effects. The items these variables currently cover are:
--t: ThemeTheme maintainers may change in the theme files
Variables used in the Web Components that are derived from the Platform's theme. These CSS variables cover things like:
--tct: OverrideAlways safe to change
These are exposed to provide granular areas of styling customization on components that depart from the default appearance. Some examples might include:
border-radiusmarginWith our use of variable prefixes now explained, let's look at a real example of how Tecton uses these variables to style components. In this example, we'll be looking at the q2-btn styles for the :disabled state.
button:disabled {
opacity: var(--tct-btn-disabled-opacity, var(--app-disabled-opacity, 0.4));
}You can see the CSS selector button:disabled has a cascade of fallback variables just for opacity. Stacks like this exist for many CSS properties on every component, but let's take a look at this stack and walk through each option.
--tct-btn-disabled-opacity - This is the component level override in the event you want something different than what the theme has set.--app-disabled-opacity - This is an application generic variable that is set by the application and is used as the platform's default.0.4 - This is a fallback value that is used as a last resort if all other variables are not set.This is how we approach CSS settings across the component system. At the base is a variable chosen to match the intended use. But why this approach? CSS variables have a couple of advantages. They provide the cascading behavior we've just talked about, allowing for platforms and Tecton to provide sane defaults and cascade color choices. But just as important is their ability to pierce the shadow DOM.
All Tecton components are web components, meaning they hold markup in their own private DOMs, called the shadow DOM. CSS is scoped to that DOM, which is great for preventing naming conflicts but not great for efficient CSS. CSS vars can be applied to any element, and they cascade to all contained elements, regardless of light or shadow DOM.
Not all component properties are available for override, but most are, and more are being added all the time.
To style Tecton to match a platform you need to create a theme file that overrides Tecton's variables.
Tecton has a set of standardized color variables that platform authors/theme creators must provide so that extension authors can expect to reliably use those variables. The system does not enforce how these colors are used, however.
This can get confusing in practice, so let's look at an example. Tecton expects a --t-primary variable. The theme file given to the connect call is injected as an inline style tag so that variable becomes available for use anywhere in an extension. The primary q2-btn also uses that variable as its background color.
Every platform has a different setup, but we feel it is important to explain how theming variables are established in UUX and then provided to modules that render within that platform.
Because UUX is a large platform, it has a complex theming system that we utilize to provide a consistent look and feel across the platform; whether you're looking at a module being loaded into the UI or a feature that is native to that platform (i.e., not inside of an iframe).
Within every theme scss file, theme maintainers have the ability to set any number of variables that are used throughout the platform to change the look and feel of various features.
Some of these variables are one-offs, only used in one or two places. While others, like the ones that control the primary and secondary colors, are used throughout the whole codebase.
Each of these variables has a basic default so that if a theme doesn't provide a specific value for them, the feature will still look good.
Over time Tecton has isolated a subset of variables that it uses for styling its web components. Using the standard variables within the theme, we set each of these as CSS variables within a dedicated slot in the theme's stylesheet. Then at build-time, all those variables are compiled into native CSS.
After building the app and its stylesheets, we have one extra build step that reaches into that dedicated slot in the theme's stylesheet, and copies all those variables, and a small library of utility CSS classes, into a separate stylesheet.
This stylesheet is what is fetched, and subsequently injected into the module when it makes the connect call to get set up with the platform. With these variables now set in the module, the web components are able to make use of them, which ensures that the components that render there look identical to the ones used at the platform level. Leading to a seamless experience for the user.
Additionally, those building their own modules can use either the CSS variables or the utility classes to style their features. This allows them to rest assured that whatever patterns are being utilized by the platform are getting used by the module as well, without them needing to think about it.
In addition to the theme's brand-identity layer described above, Tecton applies an aesthetic — a separate CSS-custom-property layer covering structural traits like corner radii, border treatments, drop shadows, and density. The SDK loads the host platform's active aesthetic into the extension iframe automatically after connect(), so extensions render with the same structural look-and-feel as the rest of the platform without each extension needing to ship per-aesthetic styling itself.
When an extension's SDK version supports aesthetics but the host platform doesn't advertise the aesthetic capabilities, the SDK falls back to loading the standard aesthetic, a backward-compatible look that replicates the pre-2025 Helix Slate / UUX style.
Extension code can read the active aesthetic via tecton.sources.getThemeInfo and subscribe to changes via tecton.sources.themeInfoChanged. For the platform-side capabilities, the catalog of available aesthetics, and the SDK-Behind shim pattern for platforms supporting older extension SDKs, see Aesthetics.
Use the data-outlet-selector attribute Tecton sets on each extension's <body> to scope rules to extensions rendered in a specific outlet:
body[data-outlet-selector="tecton-overpanel"] {
--tct-btn-padding: 4px 8px;
}The attribute's value comes from outlet-selector="..." set on the platform's outlet element and is plumbed through Tecton automatically.
For the full pipeline, UUX-specific placement guidance, and the list of selector values used by UUX, see Outlet Selectors.
When combined with media queries, CSS variables can dynamically adjust styles based on a variety of conditions, such as viewport size, device type, or external factors like dark mode preferences.
Let's explore how to use CSS variables in conjunction with media queries to target specific scenarios: print, iOS, Android, dark mode, and container queries.
At minimum, you want to provide a standard set of variables that will be used when building out your theme. In our examples, we're going to be modifying the background color of the q2-btn when the intent="workflow-primary". We do this by using the :root block in CSS.
:root {
--tct-btn-primary-background: #000000;
}A standard approach to crafting responsive themes is to use media queries that check the device width. Here's a common set of breakpoints for mobile, tablet, and desktop:
/* Mobile devices */
@media (max-width: 767px) {
:root {
--tct-btn-primary-background: #110000;
}
}
/* Tablet devices */
@media (min-width: 768px) and (max-width: 1023px) {
:root {
--tct-btn-primary-background: #220000;
}
}
/* Desktop devices */
@media (min-width: 1024px) {
:root {
--tct-btn-primary-background: #330000;
}
}Modern operating systems allow users to choose a dark color scheme. With media queries, we can detect this choice and adjust our web design accordingly.
@media (prefers-color-scheme: dark) {
:root {
--tct-btn-primary-background: #440000;
}
}Printing a web page often requires different styling compared to its on-screen presentation. CSS offers a print media type to cater to this.
@media print {
:root {
--tct-btn-primary-background: #550000;
}
}Container queries provide styling capabilities based on the container's size rather than the viewports. While a new feature in all modern browsers, they are now widely supported and available for use.
.my-container {
container-type: inline-size;
}
@container (min-width: 100px) {
button {
--tct-btn-primary-background: #660000;
}
}