Themes
XMLUI has a powerful theme system that allows you to change the default theme. XMLUI themes are multi-tone: a single theme file defines color variants for the light and dark tones.
XMLUI ships with a few themes; you can check them here.
In this article, you will learn the basics of XMLUI's theme management, including the themes' structure, theme variables, and theme inheritance.
Theme IDs and Tones
Each theme has an identifier. To apply a particular theme, change the "defaultTheme" property in the app's configuration file to its ID. By default, an app uses the light tone; however, you can change it to dark by setting the "defaultTone" property to "dark".
The following sample uses the default theme (its ID is "xmlui") with the default light tone:
Change the following properties in the configuration file:
{
"defaultTheme": "xmlui-purple",
"defaultTone": "dark"
}
Now, you see the purple-shaded XMLUI theme with a dark tone.
What a Theme Contains
Themes have a simple structure; they have a few properties and define theme variables and resource items. You can define a theme in a JSON file and add it to an app. A theme's JSON file has the following structure:
{
"id": "<the unique theme ID>",
"name": "<the optional theme name>",
"extends": ["<base theme ID 1>, <base theme ID 2>", "<...>"],
"themeVars": {
"<theme variable name>": "<theme variable value>",
"<...>": "<...>",
"light": {
"<light tone specific theme variable name>": "<theme variable value>",
"<...>": "<...>"
},
"dark": {
"<dark tone specific theme variable name>": "<theme variable value>",
"<...>": "<...>"
}
},
"resources": {
"<resource name>": "<resource value>",
"<...>": "<...>"
}
}
Simply said, a theme is a set of theme variables and resource definitions. Let's discuss the properties within a theme definition:
id
: This property is the unique identifier of the theme, a string.name
: You can optionally provide a descriptive name for a theme; the framework may use it in the UI.extends
: You do not have to define each theme from scratch; a theme can extend existing themes by defining only those theme variables and resources it intends to modify.themeVars
: The theme variables that constitute the particular theme.resources
: The resources defined in the particular theme.
In this article, we discuss only theme variables. You can learn more about resources in this article.
When you define theme variables, you can separate tone-independent values from tone-dependent ones. In most cases, tone-dependent variables are colors or color-including values, such as color, background color, shadow, etc. You can separate the color-dependent theme variables by using the "light" and "dark" property names as the following example shows (using theme variables of a fictional MyComponent
):
{
"themeVars": {
"fontSize-MyComponent": "2rem",
"borderRadius-MyComponent": "8px",
"borderStyle-MyComponent": "solid",
"light": {
"backgroundColor-MyComponent": "white",
"textColor-MyComponent": "black"
},
"dark": {
"backgroundColor-MyComponent": "black",
"textColor-MyComponent": "white"
}
}
}
Theme Scopes
You can quickly change an app's theme by configuring a different theme in the app's configuration file. In addition to using the app-level theme, you can apply other themes for a particular part of the app.
Use the Theme
component to wrap a part of the markup and apply a modified theme. With Theme
, you can change the theme, modify the tone, or even alter particular theme variables.
Let's see a few samples!
The following markup displays the first button's panel with the default theme and the second panel with the "xmlui-green" theme:
<App>
<HStack>
<Button>Button with default theme</Button>
<Theme themeId="xmlui-green">
<Button>Button with xmlui-green theme</Button>
</Theme>
</HStack>
</App>
The next sample demonstrates that you can use the tone
property of Theme
to set a different tone for a UI patch.
<App>
<Theme tone="light">
<Card title="Card with light tone" />
</Theme>
<Theme tone="dark">
<Card title="Card with dark tone" />
</Theme>
</App>
In addition to theme IDs and tones, you can modify particular theme variables, as the following sample demonstrates:
<App>
<Theme color-primary="orangered" borderRadius-Card="40px">
<Card title="Card with custom text color and border radius" />
</Theme>
<Theme tone="dark" color-primary="mediumpurple">
<Card title="Card with dark tone and custom text color" />
</Theme>
</App>
Creating Themes
You can quickly create new themes by adding a theme file (with a .json
extension and JSON syntax) to the app's themes
folder. The file name should be the same as the id
within the theme file.
XMLUI fetches theme files automatically using this naming convention and looks for them in the themes
folder. You
can change the location and the name of the theme file, but in this case, you need to modify the app's configuration
file. For more information, see the article.
Earlier, you learned that an XMLUI theme file contains theme variables with their theme-specific values. Though the framework works with several hundred theme variables, creating a new theme does not require defining each of them!
When you create a new theme, XMLUI automatically inherits from the default theme, and thus, you need to list only the altered theme variable values.
Let's assume you run this app with the default theme:
<App>
<H1>AcmeUI App</H1>
<Text>Welcome to the AcmeUI intranet home page!</Text>
<Button label="Search for more information" />
</App>
Create a new theme named "my-brand" and save it into the themes/my-brand.json
file within the app's folder:
{
"id": "my-brand",
"name": "My Brand Theme",
"themeVars": {
"fontFamily": "serif",
"fontSize": "1.2rem",
"fontFamily-Heading": "sans-serif",
"fontWeight-H1": "fontWeight-medium",
"borderRadius": "8px",
"light": {
"color-primary": "purple",
"color": "black"
},
"dark": {
"color-primary": "mediumpurple",
"color": "white"
}
}
}
This theme definition declares color variants for the light and dark tones. Do not worry if you miss the exact meaning of theme variable values. Later in this article, you will learn about how to understand them.
Set this new theme as the default in your app's configuration file:
{
"defaultTheme": "my-brand"
}
Now, your new theme displays the app in a light tone like this:
It also handles the dark tone:
Theme Variable Semantics
The concept of theme variables is pivotal in XMLUI. When you style an app, a component, or a particular part of the app, you do it with theme variables.
XMLUI allows the creation of new themes and the customizing of existing ones with the least amount of effort. To support this, theme variables have a unique naming convention that the engine leverages when rendering visual components.
Each theme variable name follows a naming convention to leverage the arsenal of features the theming engine provides:
<propertyName>-<ComponentId>-<trait1>-<trait2>--<state1>--<state2>--<stateN>
In this convention, the <ComponentId>
part is optional.
- Those theme variables that do not use the
<ComponentId>
segment are app-bound variables, so they define visuals that can be applied to the entire application, including multiple types of components. - Variables with a
<ComponentId>
segment are component-bound variables, which influence only the component's appearance defined by the corresponding ID.
You can define theme variables that do not follow this convention; we call them unbound variables. You can still use them; however, the theming engine simply replaces their occurrences with their values without utilizing intelligent feaures provided for app-bound and component-bound variables.
Properties, Traits, and States
Each theme variable is used to define the value of a property influencing the visual appearance of a particular part
of the UI. The <propertyName>
segment defines such a visual property.
Here are a few examples:
color
: text color (or forecolor)backgroundColor
: background colortext-size
: the size of the textlineHeight
: the height of a text linepaddingHorizontal
: The value declaring the horizontal padding space
The <propertyName>
segment should use lowercase letters and dashes.
For the complete list of available <propertyName>
values, see the Theme Property
Names article under "Theme Variables".
The <trait1>
and <trait2>
segments describe one or two traits regarding a particular component.
For example, the Button
component has two traits that define its appearance. The themeColor
trait defines theset
of colors to use with the button; its available values are primary
, secondary
, and attention
. Thevariant
trait specifies the general appearance of the button (whether it has a border at all, etc.) with thevalues of
solid
, outlined
, or ghost
. The themeColor
is the first trait, and variant
is the second. So, wecan use
(among others) these theme variables to modify button colors:
backgroundColor-Button-primary-solid
: Defines the background color of a button that is set up to use theprimary
theme color, displayed with thesolid
variant.borderColor-Button-attention-ghost
: Specifies the border color of a ghost button displayed with theattention
theme color.
See the App-Bount Traits article under "Theme Variables" for the complete list of app-bound traits. You can check the traits of a particular component on its reference page.
The <state1>
, ..., <stateN>
values define visual states that may modify the appearance of a particular UI element or component (regardinga
specific trait). Here are a few examples:
hover
: The mouse is hovered over the componentactive
: The particular component is being activated by the user (for example, a user presses down the mouse button while over a button element)focus
: The particular element (e.g., a textbox) currently has the focus
When defining a theme variable, you can define multiple state values (in any order). Let's see a few examples:
backgroundColor-Button-primary-solid--hover
: The background color to use with asolid
button and with theprimary
theme color while the mouse hovers over it.borderColor-Button-attention-ghost--focus
: Specifies the border color of a focused ghost button displayed with theattention
theme color.
For the complete list of available <state>
values, see the Visual States article in the "Theme
Variables" reference.
Theme Variable Chaining
Earlier, you have learned that theme variables following the naming conventions above are either app- or
component-bound. When the name contains a <ComponentId>
section, it is bound to the component with that ID. The
component ID is always the name of the component (with the exact case) as you use them in the markup. For example,
Button
for the <Button>
component, Card
for the <Card>
component, and so on.
The individual XMLUI components are built to leverage as many app-bound theme properties as possible. Their design ensures that you can change their visual appearance by modifying only a few theme variables and also allows you to fine-tune component styles.
The theming engine uses several techniques to achieve this behavior. One of them is theme variable chaining.
This approach means that theme variables compose a chain and most fall back to a previous variable on that chain that is more generic. For example, here is a chain that sets the border color of a solid button using the primary color when the mouse hovers over it:
color-primary
textColor-Button
backgroundColor-Button
backgroundColor-Button-solid
backgroundColor-Button-primary
backgroundColor-Button-primary-solid
backgroundColor-Button-primary-solid--hover
Theme variable chaining has two significant behaviors:
- Traversal from the chain's last (bottom) element toward the first (top). When the engine looks for a particular theme variable value, it searches it from the bottom toward the top until it finds the particular variable value to use.
- Tail pruning. When you set the value of a theme variable somewhere in the chain, the other variables toward the bottom of the chain get cleared (they lose their values).
Components may add logic to generate undefined lower-level theme variable values from upper-level values. For
example, the Button
component automaticaly creates fallback colors when the textColor-Button
theme variable is
set.
Changing App-Bound Variables
Let's examine these behaviors with examples! Here, you see a gallery of assorted buttons, a badge, and a few text components using the default theme (light):
Let's update the value of color-primary
. This theme variable is at the top of the theme variable chain.
{
"color-primary": "orangered"
}
Due to the theme variable chaining, all components using the primary color (Button
instances with their theme
color property set to primary
and Badge
leverage the primary color) immediately change their appearance:
Try moving the mouse over the buttons labeled as primary and click them! You can see that the change in the
color-primary
theme variable affects other button colors, like the one used to indicate the hovered and pressed
states. The theming engine ensures that the changes in color-primary
update the related visual properties
automatically. Besides the hover color, the border color changes, too.
Changing Component-Bound Variables
Changing color-primary
updated the primary color of all components, including the Badge
. Suppose you want to
keep the Badge
component intact, because you intend to change only button colors. In that case, you should use the
more specific textColor-Button
theme variable, which (as it is a component-bound theme variable), affects only the
Button
component:
{
"color-Button": "orangered"
}
The result demonstrates that the Badge
component remained intact. However, all buttons, independently of their
color theme (primary
, secondary
, or attention
) and variant (solid
, outlined
, or ghost
) use the same
colors.
The Button
component responds to the changes of the textColor-Button
and updates the background, text, and
border colors accordingly. It also updates the colors to use with the hover and active states.
More Specific Variable Changes
The single change of textColor-Button
redefined all button colors, which goes against the intention of having several
theme colors. If you intend to change only the solid button using the primary color, the best theme variable to
modify is textColor-Button-primary-solid
:
{
"textColor-Button-primary-solid": "orangered"
}
The name of the property tells everything about its effect. Here, you can check that it affects only one of the buttons, as expected. Buttons with secondary and attention colors or using the outlined and ghost variants are intact.
Sometimes, you want to override a single visual property of a component, like the background color of the button when it is hovered:
{
"backgroundColor-Button--hover": "orangered"
}
This theme variable works as you expect! Move the mouse over any button to test the hover effect:
Following this pattern, you can tighten the theme variable to affect only a single button type's hover color, like in this sample:
{
"backgroundColor-Button-primary-solid--hover": "orangered"
}
Try the hover effect now! You can see now it affects only the top-left button (using the primary color theme with the solid variant):
Component Theme Classes
Components can form a theming hierarchy. For example, many components represent some input fields, as the following example shows:
<Form id="driveForm" data="{{car: 'Toyota Celica', year: '1999'}}" padding=1em>
<FlowLayout paddingBottom=1em gap=1em>
<FormItem width="50%" label="Car Type" bindTo="car" autoFocus="true" type="text"/>
<FormItem width="50%" label="Year" bindTo="year" autoFocus="true" type="integer"/>
</FlowLayout>
</Form>
If you want to change their border rounding while keeping other components, such as the buttons intact, you could create a derived theme like this:
{
"id": "rounded",
"name": "Rounded theme",
"themeVars": {
"borderRadius-TextBox": "160px",
"borderRadius-NumberBox": "160px"
}
}
The new theme works as you expect:
Many components function as inputs. When you change some of their more generic visual attributes, such as the rounding of borders, you will likely change the same attribute of multiple component types. For example, you changed the border rounding of two components in the previous sample.
What if you intend to change the rounding of ten input component types? Following the pattern above, you would endup
with a theme with different borderRadius- <component-name>
theme variables.
XMLUI provides a faster way to do this. Define the theme like this:
{
"id": "rounded",
"name": "Rounded theme",
"themeVars": {
"borderRadius-Input": "160px",
"backgroundColor-Input": "papayawhip"
}
}
This theme changes the rounding and the background color of all input components:
The key to this behavior is the Input
component name within the borderRadius-Input
and backgroundColor-Input
variable
names.
There is no Input
component. Input
is just an abstract theme class name used from theming. The TextBox
,
NumberBox
, and other input components inherit visual attributes from this abstraction. This abstraction adds new
theme variables to the theme variable chain of input components.
For example, the border rounding-related theme variable chain is the following for the TextBox
:
borderRadius
borderRadius-Input
borderRadius-TextBox
The reference information of components describes the theme classes a particular component inherits from.