Frequently asked questions regarding theming in OpenUI5
Theming.applied event fire?The Theming.applied event timing behavior can be confusing, especially if it fires synchronously and not asynchronously.
Note:
The
Theming.appliedevent ensures consistent behavior: If you attach a handler, it will always be called regardless of whether the theme is already loaded or needs to be loaded. If the theme is already fully applied when you attach it, your handler executes immediately. If a theme change or new library CSS loading is in progress, your handler will be called when the loading completes; this way, all listeners are notified consistently.
Caution:
Developers must not rely on the synchronous execution behavior, as it can vary depending on the current state of theme loading. Always write your code in a way that it can handle both synchronous and asynchronous execution patterns.
Synchronous Firing:
The event fires immediately (synchronously) if the following applies:
All required CSS files are already loaded
No CSS requests are pending for the current theme
Theme switching doesn’t require additional CSS loading
Asynchronous Firing:
The event fires after CSS loading completes if the following applies:
New CSS files need to be loaded for the theme
Libraries are loaded dynamically after theme application
Theme switching requires fetching CSS from external sources
For Self-Triggered Theme Changes:
JavaScript:
sap.ui.require(["sap/ui/core/Theming"], function(Theming) {
// 1. Change theme FIRST
Theming.setTheme("sap_horizon_dark");
// 2. THEN attach listener
function onApplied() {
console.log("New theme applied:", Theming.getTheme());
// Clean up listener after handling
Theming.detachApplied(onApplied);
}
Theming.attachApplied(onApplied);
});
TypeScript:
import Theming from "sap/ui/core/Theming";
// 1. Change theme FIRST
Theming.setTheme("sap_horizon_dark");
// 2. THEN attach listener
const onApplied = () => {
console.log("New theme applied:", Theming.getTheme());
// Clean up listener after handling
Theming.detachApplied(onApplied);
};
Theming.attachApplied(onApplied);
Why Order Matters:
If you attach the callback before setting the theme, the callback might immediately be called for the currently applied theme before your required change takes effect.
The event fires exactly once per theme application cycle.
It’s safe to perform DOM manipulations in the event handler.
The event indicates that all theme-related CSS is loaded and applied.
Custom CSS (custom.css) is guaranteed to be loaded last.
This happens if the applied event fires synchronously, which can occur in several scenarios.
Theme Already Applied:
// If "sap_horizon" is already the current theme
Theming.attachApplied(function() {
// This fires immediately because the theme is already applied
console.log("Handler called immediately!");
});
No CSS Loading Required:
// If switching to a theme that's already cached/loaded
Theming.setTheme("sap_horizon_dark");
Theming.attachApplied(function() {
// May fire immediately if CSS is already available
console.log("Theme switch completed immediately!");
});
Check if Theme Change is Needed:
JavaScript:
sap.ui.require(["sap/ui/core/Theming"], function(Theming) {
var targetTheme = "sap_horizon_dark";
var currentTheme = Theming.getTheme();
if (currentTheme !== targetTheme) {
Theming.setTheme(targetTheme);
Theming.attachApplied(function onApplied() {
console.log("Theme changed to:", Theming.getTheme());
Theming.detachApplied(onApplied);
});
} else {
console.log("Theme is already set to:", currentTheme);
}
});
TypeScript:
import Theming from "sap/ui/core/Theming";
const targetTheme = "sap_horizon_dark";
const currentTheme = Theming.getTheme();
if (currentTheme !== targetTheme) {
Theming.setTheme(targetTheme);
const onApplied = () => {
console.log("Theme changed to:", Theming.getTheme());
Theming.detachApplied(onApplied);
};
Theming.attachApplied(onApplied);
} else {
console.log("Theme is already set to:", currentTheme);
}
Universal Pattern (Both Cases Handled ):
JavaScript:
sap.ui.require(["sap/ui/core/Theming"], function(Theming) {
function handleThemeApplied() {
console.log("Theme is ready:", Theming.getTheme());
// Your theme-dependent logic here
}
// This pattern works whether the event fires sync or async
var targetTheme = "sap_horizon_dark";
Theming.setTheme(targetTheme);
function onApplied() {
handleThemeApplied();
Theming.detachApplied(onApplied);
}
Theming.attachApplied(onApplied);
});
import Theming from "sap/ui/core/Theming";
const handleThemeApplied = () => {
console.log("Theme is ready:", Theming.getTheme());
// Your theme-dependent logic here
};
// This pattern works whether the event fires sync or async
const targetTheme = "sap_horizon_dark";
Theming.setTheme(targetTheme);
const onApplied = () => {
handleThemeApplied();
Theming.detachApplied(onApplied);
};
Theming.attachApplied(onApplied);
Always set the theme first, then attach the listener
Be prepared for both synchronous and asynchronous event firing
Clean up event listeners after handling to avoid memory leaks
Use the universal pattern when you’re unsure about timing behavior
While there is always the option to create a new theme, this is overkill for most minor style adaptations. For those minor changes, the recommendation is to include additional CSS into the page which changes the style of the respective tags of the OpenUI5 control. This allows complete, arbitrary changes of the visual design - after all it is the same technology that the UI5 controls use for their styling.
The main options are the following:
.addStyleClass("myStyle") on some control instances if you want only those instances to look different from other instances - and then write CSS code that refers to the normal classes/tags and to the CSS class you just added.Note:
- With this high degree of power and flexibility comes quite some responsibility. With CSS you can easily break the functionality of a control. This is not OpenUI5-specific, but when you make CSS adaptions, you should always have good knowledge of this open standard.
- The inner structure of a control, the tag hierarchy, the IDs and CSS classes are not part of the public control API for which we guarantee stability. This is also the case for other libraries which might define some CSS classes as stable, but not everything else. As CSS can refer to the inner structures of a control, you have to accept the risk that your style changes break when we change the inner structure. Changing the inner structure is a freedom we absolutely need to reserve, so we can fix bugs and add features of a control.
- When your CSS does not work as expected, use the developer tools in your browser to inspect the page and check which CSS rules are applied to the respective tag, and which rules might be applied but are overridden by other rules. If your rules are overridden by other rules, this is probably due to their order of appearance (last rule wins) or the CSS selector specificity (more specific CSS selectors win).
DON’Ts
When OpenUI5 is used in a standard way, which means loaded by a<script> element in the <head> of a page, and all libraries declared in the respective attribute of the script tag), it is sufficient to just add the custom CSS to any place after the OpenUI5 <script> element. OpenUI5 will insert its CSS links immediately after the <script> tag, so any subsequent CSS will appear further down in the DOM and can thus overwrite the OpenUI5 CSS.
However, it is important to understand the precedence rules of CSS: The order of appearance is not the only factor that determines which one of two or more conflicting rules wins. Actually it is only the least important factor. The most important (and maybe least known) factor is the specificity of the selector belonging to a rule.
For example, if one rule says button {color:red;} to make all button texts red, and a second rule says div > button {color:green;} to make all button texts, which are direct children of a <div> element, green, the second rule always wins because it is more specific. The order of appearance in the DOM does not matter in this case. It would only matter if both rules started with an equal selector, such as button{color:***}.
The order of loading is completely irrelevant, only the position in the DOM counts in this case. If you load OpenUI5 without a <script> tag in the <head>, or if you do not specify all used control libraries in the <script> tag, but loaded some of them later on when the body was already loaded, you can still make sure a custom CSS appears further down in the DOM by loading it with sap.ui.dom.includeStyleSheet(stylesheetUrl[, id])after loading OpenUI5 or the dynamically loaded control library.
Related Information
style property where I can make arbitrary changes?A control usually does not map to one HTML element, but to a whole tree of HTML elements. Whatever is set for the style property would probably be added to the root element of this HTML tree, and only there, so there is no style access to inner parts. If you just want to override the height of a button, this would actually work. But as soon as a change is a bit more complex, it will not work that easily. A more complex change is, for example, adapting the height of a ComboBox control. The outer <div> will get the proper height. And incidentally also the <input> tag inside, as it has 100% height set. But the dropdown arrow and the respective button-kind-of-thing has a fixed height, and the whole control will look pretty broken then.
In other cases, when HTML elements that break the CSS inheritance chain are nested, for example, <table> and font settings, you can change style to a different font and text color, but it will simply do nothing.
In general, we try to expose the obvious adaptation content in the API, for example, the button height. But the less obvious adaptations might have to be supported from inside the control to work properly, and as we cannot foresee and support everything you can do with a style property, we raise the bar a little bit by requiring you to write CSS (potentially using .addStyleClass(…) for the respective control). With CSS you can do what you cannot do with a style property: tweak the inner HTML components of a control.
Applications (at least the more traditional ones – currently this seems to be less of a rule, but I’m not sure it will stay like this forever) need to conform to some visual design guideline and, in general, it is not even desired that applications change the TextField height or use font just the way they like. As you can use CSS, UI5 still supports that, but we shouldn’t make breaking the visual design a rule in our official API.
If you want to change some styling and use control.addStyleClass(…) to add a CSS class, but it does not seem to work, you first have to pin down exactly what is not working:
You can check this by inspecting the HTML with your browser’s developer tools.
sap.ui.core.Element). Only some of them support addStyleClass.On all OpenUI5 application pages, the HTML root tag of the DOM gets the additional attribute data-sap-ui-browser where the value is the type and the current browser version. When browser-specific CSS needs to be written, this attribute can be used in CSS selectors.
html[data-sap-ui-browser*="sf"] button { /* this rule will only be applied if the current browser is ANY version of Safari */
padding-top: 0px;
}
There is not one single way to create a new theme, but there are several options. Which one you choose depends on several factors:
Depending on the answers it might be better to not even create a new theme but just adapt an existing one.
Theme configuration problems can manifest in various ways. Here’s a systematic approach to diagnose and resolve common issues:
Configuration Check:
JavaScript:
// Verify current theme
sap.ui.require(["sap/ui/core/Theming"], function(Theming) {
console.log("Current theme:", Theming.getTheme());
});
// Check if theme is fully applied
sap.ui.require(["sap/ui/core/Theming"], function(Theming) {
Theming.attachApplied(function() {
console.log("Theme applied successfully:", Theming.getTheme());
});
});
TypeScript:
import Theming from "sap/ui/core/Theming";
// Verify current theme
console.log("Current theme:", Theming.getTheme());
// Check if theme is fully applied
Theming.attachApplied(() => {
console.log("Theme applied successfully:", Theming.getTheme());
});
Inspection of Theme Root Configuration:
JavaScript:
// Check theme roots in browser console
console.log("Theme roots:", window["sap-ui-config"]?.["theme-roots"]);
TypeScript:
// Check theme roots in browser console
console.log("Theme roots:", (window as any)["sap-ui-config"]?.["theme-roots"]);
TypeScript:
// Check theme roots in browser console
console.log("Theme roots:", (window as any)["sap-ui-config"]?.["theme-roots"]);
Duplicate CSS Requests:
Cause: Mismatch of preloaded links and OpenUI5-computed URLs
Solution: Ensure version parameters match, verify theme names, and check path consistency.
404 Errors:
Cause: Incorrect theme root URLs or missing theme assets
Solution: Verify the theme root accessibility, check the spelling of theme names, and confirm asset availability.
Wrong Theme Applied:
Slow Initial Loading:
Check if preloading is beneficial for your specific use case
Monitor network requests in browser developer tools
Consider lazy loading for non-critical components
Bootstrap Parameter Check:
<!-- Ensure theme name matches theme root configuration -->
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="myTheme"
data-sap-ui-theme-roots='{"myTheme": "https://correct.service.com/"}'>
</script>
Security Configuration:
<!-- Verify allowed origins match theme root domains -->
<meta name="sap-allowed-theme-origins" content="https://trusted.service.com,https://backup.service.com">
CSS performance problems can cause significant UI freezing, especially when working with large tables or datasets. These issues are often browser-specific and might be more pronounced in certain environments.
UI freezing during scrolling or interaction
Delayed response to user actions
Browser-specific performance degradation (for example, Edge versus Chrome)
Performance issues that get increasingly worse with dataset size
Application and animation freezing
Use Browser Developer Tools:
Most browsers provide performance analysis tools that can help identify CSS-related performance issues. Access your browser’s developer tools and look for performance profiling features. During the problematic interaction, record a performance trace to identify long-running style recalculation tasks and CSS selectors with high invalidation counts.
Common Performance Anti-Patterns:
Expensive pseudo-selectors, such as :has(), :not(), :nth-child(), :nth-of-type()
Universal selector (*) with complex hierarchies or properties expensive for the browser’s rendering pipeline
Broad selectors, for example, matching thousands of elements unintentionally (for example, generic tag selectors like div, tr, td, or span without proper scoping)
Deeply nested descendant selectors
Overly complex mix of CSS math functions:
width: calc(100vw -
var(--_my_custom_lib_sidebar) - max(var(--_my_custom_lib_padding), 2rem) * 2 -
min(var(--_my_custom_lib_margin), 3vw) ...)
A real-world example involves a CSS selector that causes severe performance degradation:
/* Problematic selector - affects all tables */
table:has(.searchSuggestion) td,
table:has(.searchSuggestion) .sapMLIBShowSeparator > td {
border-top: none;
}
Why This Causes Issues:
The :has() pseudo-selector is computationally expensive
Style recalculations become increasingly costly with growing dataset size
Restrict Selector Scope
/* Better - restrict to specific control scope */
.mySearchField table:has(.searchSuggestion) td,
.mySearchField table:has(.searchSuggestion) .sapMLIBShowSeparator > td {
border-top: none;
}
Use More Specific Selectors:
/* Alternative approach - target specific elements */
tr.searchSuggestion td,
tr.searchSuggestion .sapMLIBShowSeparator > td {
border-top: none;
}
Avoid Expensive Pseudo-Selectors:
If possible, replace expensive pseudo-selectors with class-based targeting:
* Instead of using :has(), add specific classes */
.search-suggestion-table td {
border-top: none;
}
To validate CSS performance improvements, create test scenarios with large datasets (more than 500 table rows) and monitor performance across different browsers using their developer tools to profile CSS recalculation time. Compare performance before and after applying your CSS changes by first recording a baseline measurement with the current CSS, then temporarily disabling suspect CSS rules to isolate the issue, and finally verifying the improvement after implementing your solution. Cross-browser testing is essential to ensure fixes work consistently across your target browsers.
User reports of UI freezing or sluggish response
Performance degradation after CSS changes
Browser-specific performance issues
Increasing number of issues as datasets grow
Tip:
If you suspect CSS performance issues, start by creating a minimal reproduction case with a large dataset. This helps isolate the problem and validate potential solutions before implementing them in your application.
For comprehensive best practices on CSS performance and theming, see Creating Themable User Interfaces.