Last updated Dec 5, 2023

Getting started with Atlassian Theme

CSS to support both color modes

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
2
html[data-color-mode="light"] {
    /* whatever styles you want */
}
If fallbacks are providing when using tokens, then this is safe for both `original` and the new `light` theme because there will be no token values and the fallbacks will be used.

To write styles only applied in dark mode, use the following selector

1
2
html[data-color-mode="dark"] {
    /* whatever styles you want */
}

CSS to support a specific theme

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
2
html[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.

Adding theme support to pages

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
2
import 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

Using iframes with consistent theming

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
2
const 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: