In ArcGIS Experience Builder, most theme customizations can be achieved by modifying theme options in the theme/variables.json. However, if you want to create a more unique and differentiated theme, customizing theme options may not be sufficient; you can customize each component's style to meet your design needs. Component styles in the Experience Builder theme are built using Emotion styled API, allowing you to create dynamic styles based on the runtime theme and component props.
1. Override component or global styles
To customize styles in an Experience Builder theme module, create or edit the style.ts file located at path to theme/style.ts. Depending on your needs, you can choose to:
- Override component styles: Export style objects for specific base components you want to customize.
- Add global styles: Use the special
Csscomponent to set global CSS rules, such as font faces or resets, that affect all components in your application.Baseline
You can use either approach or both, depending on what you want to customize.
2. Validate and optimize
After customizing component styles and adding global styles, it is important to validate and optimize your theme to ensure it meets accessibility standards and maintains a consistent user experience.
Customizing base component styles
Base components refer to components that can be directly imported from jimu-ui module and whose styles can be overridden in ArcGIS Experience Builder theme module. You can directly customize these base component styles by creating and exporting style objects in path to theme/style.ts.
Code example: Customize styles for a Button component:
// style.ts
import type { ThemeComponentStyleOptions } from 'jimu-theme'
export const Button: ThemeComponentStyleOptions['Button'] = {
root: ({ theme }) => {
return {
// Your custom styles here
}
}
}Style function parameters (root):
| Parameter | Type | Description |
|---|---|---|
| theme | Theme | Resolved runtime theme object (tokens + computed values). |
| styleState | any | Component props/state (variant, color, size, tag, etc.). |
| props | any | Original React props (when exposed). |
| ref | any | Reserved (internal use). |
Return value can be:
- Plain object (recommended).
- Template string (for raw CSS with selectors).
Here are some key points to consider:
- The exported object name must match the component name, the object key is fixed to "root".
- Use
Themeto get the proper type definitions for the component style object.Component Style Options - The style value is a function that returns an Emotion-compatible style object:
theme: the runtime theme object, containing theme variables.style: the component's runtime state, which equals the props of the component. You can refer to the props of each component underState Components/jimu-ui/index.
Code example: Dynamically change Button styles based on variant and color
// style.ts
import type { ThemeComponentStyleOptions } from 'jimu-theme'
export const Button: ThemeComponentStyleOptions['Button'] = {
root: ({ styleState, theme }) => {
const isPrimaryContained = styleState.variant === 'contained' && styleState.color === 'primary'
const styles = isPrimaryContained
? {
backgroundColor: theme.sys.color.secondary.main,
color: theme.sys.color.secondary.text,
'&:hover': {
backgroundColor: theme.sys.color.secondary.dark
},
'&:focus-visible': {
outline: `2px solid ${theme.sys.color.primary.main}`,
outlineOffset: '2px'
}
}
: {}
return styleState.tag === 'a' ? { '&[role="button"]': styles } : styles
}
}The ternary conditional statement is required to handle the button component that uses an <a tag internally. To ensure sufficient style specificity, the [role="button"] selector is applied when the tag is <a. Most other components do not require this pattern.
return styleState.tag === 'a' ? { '&[role="button"]': styles } : stylesGlobal styles
Global styles affect the entire application and can be defined using the Css component in your style.ts file. This is useful for setting global CSS rules, such as font faces or resets.
Here is an example of using the Css component to set a global font size:
// style.ts
import type { ThemeComponentStyleOptions } from 'jimu-theme'
export const CssBaseline: ThemeComponentStyleOptions['CssBaseline'] = {
root: () => ({
html: {
fontSize: '80%'
}
})
}Additionally, the style from Css includes a root property that represents the app’s root URL. This allows you to load fonts or assets using absolute paths:
// style.ts
export const CssBaseline = {
root: ({ styleState }) => {
const rootUrl = styleState.rootUrl || ''
return `
@font-face {
font-family: 'Family name';
src: url('${rootUrl}themes/<theme name>/assets/<font file>') format('truetype');
font-weight: 400;
font-style: normal;
}
`
}
}Limitations
Typically, a custom theme can still be edited through the Experience Builder Theme Settings panel.
However, once component styles are overridden in the style.ts or style.scss, the corresponding components will no longer respond to Theme Settings adjustments.
If you are certain that your custom styles do not conflict with Theme Settings, you can explicitly allow theme editing by setting theme to true in the manifest file:
"themeCustomizable": trueBest practices
Below are some best practices to follow when customizing component styles to ensure maintainability and accessibility:
- Prefer theme options before component overrides (maintains configurability).
- Keep overrides scoped; avoid redefining universal elements (body, html) unless required.
- Use semantic tokens (
theme.sys.color.*) instead of hardcoded hex values for consistency. - Use object style form for better merging and fewer parsing errors.
- Limit use of global resets; they can impact third-party widgets.
What's next?
Learn about the different theme development options and how to create and customize themes: