It's best to do as much in CSS as possible, much less code will be written and switching between color modes will be seamless for the user.
Where possible styling should be built against the color mode rather than the current theme; again both less code will be written and this enabled future support for more themes.
To write styles only applied in light mode, use the following selector
1 2html[data-color-mode="light"] { /* whatever styles you want */ }
To write styles only applied in dark mode, use the following selector
1 2html[data-color-mode="dark"] { /* whatever styles you want */ }
Sometimes styles need an override for a specific theme in addition to what exists for the color mode.
To write an override for the original theme in light mode, use the following selector
1 2html[data-color-mode="light"][data-theme~="light:original"] { /* whatever styles you want */ }
We don't want to apply the styles unless the right color mode is active so the selector must check the color mode and the theme preference for that color mode.
Existing product pages and any pages introduced by a plugin that use Atlassian decorators should be already covered (except login, setup, and error pages).
Pages introduced by a plugin, the <html>
element attributes will have to be added. A Java API (RequestScopeThemeService#getHtmlAttributesForThisRequest
) exists so support can be added regardless of the existing page building solution used in plugins. It's easiest to use the API with Soy and pass the attributes in as a template parameter.
An example page servlet and template for illustrative purposes:
1 2import com.atlassian.soy.renderer.SoyTemplateRenderer; import com.atlassian.theme.api.request.RequestScopeThemeService; // skipping common imports for brevity public class ExampleServlet extends HttpServlet { private final SoyTemplateRenderer soyTemplateRenderer; private final RequestScopeThemeService requestScopeThemeService; public ExampleServlet( @Nonnull final SoyTemplateRenderer soyTemplateRenderer, @Nonnull final RequestScopeThemeService requestScopeThemeService ) { this.soyTemplateRenderer = requireNonNull(soyTemplateRenderer, "soyTemplateRenderer"); this.requestScopeThemeService = requireNonNull(requestScopeThemeService, "requestScopeThemeService"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Don't forget permissions checking! final Map<String, Object> soyData = new HashMap<>(); soyData.put("language", "en"); soyData.put("themeData", requestScopeThemeService.getHtmlAttributesForThisRequest(request)); soyTemplateRenderer.render( response.getWriter(), "com.atlassian.example.plugin.key:soy-web-resource-module-key", "atlassian.example.theme.root", soyData ); } }
1 2{namespace atlassian.example.theme} /** * @param language String. Language represented by BCP-47 * @param? themeData String. Attributes for server-side initialising theme preferences */ {template .root} <!DOCTYPE html> <html lang="{$language}" {if $themeData} {$themeData|noAutoescape} {/if} > </html> {/template}
See other guides to get more familiar with importing OSGi services or using soy templates or plugin development in general
The user preferences for theming may change between a page being loaded and an <iframe>
being loaded. To keep the theming consistent, it's worth passing the current request overrides as query parameters.
To mock-up how this could be done in JavaScript using the HTML API
1 2const iframeUrl = new URL('https://example.com'); const attributes = document.documentElement.attributes; const colorMode = attributes['data-color-mode'].value; const preferredThemes = attributes['data-theme'].value; const preferredDarkThemeKey = (preferredThemes.match(/dark\:([^\s]*)/i) ?? ['', ''])[1] const preferredLightThemeKey = (preferredThemes.match(/light\:([^\s]*)/i) ?? ['', ''])[1] const shouldMatch = Boolean(attributes['data-color-mode-auto']); const iframeSearchParams = iframeUrl.searchParams colorMode && iframeSearchParams.set('atlThemeColorMode', colorMode) preferredDarkThemeKey && iframeSearchParams.set('atlThemeDark', preferredDarkThemeKey) preferredLightThemeKey && iframeSearchParams.set('atlThemeLight', preferredLightThemeKey) iframeSearchParams.set('atlThemeMatchUa', shouldMatch ? 'true' : 'false')
Rate this page: