Aura: Change Individual Parts to Light/Dark Mode

I am currently implementing dark/light theme variants with Aura, based on company design tokens and custom styling.

My current setup looks like this:

  • tokens.css defines base tokens such as color scales, spacing, typography, radius, etc.
  • theme.css maps these tokens to semantic variables like surface, text, background, accent,error, success, etc. for both light and dark variants
  • styles.css uses these semantic variables to override Aura defaults
  • tailwind-custom.css also consumes the semantic variables from theme.css and is used for utility-based styling with Tailwind

So the idea is:

tokens → semantic theme variables → Aura overrides + Tailwind usage

Now I am facing the following problem.

I want to style a specific part of the application, for example only the drawer (without the navbar), with dark mode styling, while the rest of the application stays in light mode.

Additionally, if the whole application is already in dark mode, I still want full control over rendering specific sections explicitly in light or dark mode.

In the Vaadin Create 2025 presentation Aura & new theming in Vaadin 25 around minute 29, it is mentioned that individual sections can be rendered in light or dark mode independently.

Question 1

How can I render a specific area (for example Drawer, VerticalLayout, or another container) in a fixed color scheme independently from the global theme?

The goal is to render that section always in dark or always in light mode without redefining all theme-specific variables again for that area.

What is the recommended Aura-compatible approach for this?

Question 2

Does the following theme structure align with current Vaadin 25 + Aura best practices, or should this generally be structured differently?

src/main/resources/META-INF/resources/
│
├── tokens.css
│ Base design tokens
│ (color scales, spacing, typography, radius, shadows, etc.)
│
├── theme.css
│ Semantic theme variables
│ (surface, text, background, accent, interaction states)
│ mapped from tokens for light/dark variants
│
├── styles.css
│ Aura variable overrides
│ and global application styling
│
src/main/frontend/
│
└── tailwind-custom.css
for utility styling

Example css

tokens.css

:root {
    --my-neutral-0: #ffffff;
    --my-neutral-50: #f8fafc;
    --my-neutral-900: #111827;

    --my-accent-400: #818cf8;
    --my-accent-500: #4f46e5;
}

theme.css

:root {
    --my-background-color: var(--my-neutral-50);
    --my-accent-color: var(--my-accent-500);

    color-scheme: light;
}

:root[theme~="dark"] {
    --my-background-color: var(--my-neutral-900);
    --my-accent-color: var(--my-accent-400);

    color-scheme: dark;
}

styles.css

html {
    --aura-background-color-light: var(--my-background-color);
    --aura-background-color-dark: var(--my-background-color);
    --aura-accent-color-light: var(--my-accent-color);
    --aura-accent-color-dark: var(--my-accent-color);
    --aura-app-layout-inset: 0px;;
}

tailwind-custom.css

@theme {
    --color-background: var(--my-background-color);
    --color-accent: var(--my-accent-color);
}

Is this a reasonable structure, or is there a more standard/recommended approach for Aura in Vaadin 25?

Regarding:

In the Vaadin Create 2025 presentation Aura & new theming in Vaadin 25 around minute 29, it is mentioned that individual sections can be rendered in light or dark mode independently.

It looks like scoped color scheme support is limited to the App Layout component’s content area, and notifications. This documentation sections explains how to configure those: How to use color properties of the Aura theme in Vaadin.

AFAIK there is no support to modify the color scheme of arbitrary elements so that it automatically recalculates all dependent colors to respect the custom scheme on that level.

If you think that you have a specific use case for this, feel free to open a feature request here: Issues · vaadin/web-components · GitHub

1 Like

Currently reviewing a docs update related to this, and it now mentions additional ways to use a scoped color scheme. The list of selectors that allow changing the color scheme is this one: web-components/packages/aura/src/color.css at c8681c44ee581fe51958ca1ae306e8f0d55c6353 · vaadin/web-components · GitHub

So it’s supported on a number of Vaadin components and with some CSS classes, however all of these classes modify the accent color as a side effect from what I see.

Edit: Looks like this combination should do it:

  • Add aura-accent-color class
  • Apply color-scheme: dark style
  • Apply background: var(--aura-background-color) to get correct background color
1 Like

Thanks for the question! I appreciate any interest there is towards Aura and CSS in general :slight_smile:

At the time of Vaadin Create last year, Aura did allow you to set the color-scheme at any point in the DOM hierarchy. Unfortunately, due to rendering performance issues (mainly with <vaadin-grid> or components with a lot of elements), we had to register some of the base style custom properties with the native CSS <color> type (see source), which alters how the light-dark() colors are evaluated on the nodes.

The color value is evaluated on certain nodes in the DOM (the selector list where the property is defined dictates that), and the computed color value is then inherited by child nodes. Changing the color scheme on the child elements doesn’t change the inherited color value. The color scheme needs to be changed on a node that matches one of the selectors where the color properties are declared (see the color.css source Sascha linked).

Regarding your stylesheet structure, it looks fine otherwise, but you shouldn’t use [theme~="dark"] to redefine the semantic tokens for the dark color scheme. Aura relies on the native color-scheme property and therefore the use of the native light-dark() function to choose between the correct color between light and dark modes.

To also make it possible to redefine Aura’s color-scheme-specific properties (i.e., -light and -dark suffixed ones), you either need similar semantic tokens in your theme.css, or you use the “raw” values from your tokens.css stylesheet.

For example:

theme.css

:root {
  --my-background-color-light: var(--my-neutral-50);
  --my-background-color-dark: var(--my-neutral-900);
  --my-background-color: light-dark(var(--my-background-color-light), var(--my-background-color-dark));
}

styles.css

:root {
  --aura-background-color-light: var(--my-background-color-light);
  --aura-background-color-dark: var(--my-background-color-dark);
}

Or:
styles.css

:root {
  --aura-background-color-light: var(--my-neutral-50);
  --aura-background-color-dark: var(--my-neutral-900);
}

You can still have a [theme~="dark"] { color-scheme: dark; } rule in your stylesheet to override the color scheme, but like we already pointed out, that has limited usability, unfortunately.

The workaround Sascha outlined should work, with one addition: you also need to set the text color with color: var(--vaadin-text-color).

For example, to override a vertical layout to always use the dark color scheme:

<vaadin-vertical-layout class="aura-accent-color dark"></vaadin-vertical-layout>
.dark {
  color-scheme: dark;
  background: var(--aura-background-color);
  color: var(--vaadin-text-color);
}

The aura-accent-color classname does have the side effect of overriding the --aura-accent-color properties back to the global defaults/initial value. We can consider adding a classname to Aura that recomputes the color values but doesn’t have other side effects.

1 Like

Hello and thanks again for the answers and generally for always keeping an eye on the forum.

The explanation about color-scheme and the information that Aura relies heavily on light-dark() was very helpful. Based on that, I adjusted my theme.css accordingly.

In general, I should also mention that I would like to provide the tokens and theme setup to separate developer teams that are not working with Vaadin. Because of that, having a clear separation between framework-agnostic theme definitions and Vaadin-specific integration is important to me.

After the adjustments, my structure currently looks roughly like this:

tokens.css (color scales, spacing, typography, radius, shadows, etc.)

:root {
    --my-neutral-0: #ffffff;
    --my-neutral-50: #f8fafc;
    --my-neutral-900: #111827;

    --my-accent-400: #818cf8;
    --my-accent-500: #4f46e5;
}

theme.css (surface, text, background, accent, interaction states, etc.)

:root {
    color-scheme: light dark;

    --my-background-color-light: var(--my-neutral-50);
    --my-background-color-dark: var(--my-neutral-900);
    --my-background-color: light-dark(
        var(--my-background-color-light),
        var(--my-background-color-dark)
    );

    --my-accent-color-light: var(--my-accent-500);
    --my-accent-color-dark: var(--my-accent-400);
    --my-accent-color: light-dark(
        var(--my-accent-color-light),
        var(--my-accent-color-dark)
    );
}

:root[data-theme="light"] {
    color-scheme: light;
}

:root[data-theme="dark"] {
    color-scheme: dark;
}

styles.css (Aura overrides and project-specific styling)

:root {
    --aura-background-color-light: var(--my-background-color-light);
    --aura-background-color-dark: var(--my-background-color-dark);

    --aura-accent-color-light: var(--my-accent-color-light);
    --aura-accent-color-dark: var(--my-accent-color-dark);
}

.dark {
    color-scheme: dark;
    background: var(--aura-background-color);
    color: var(--vaadin-text-color);
}

:root[data-theme="light"] {
    --aura-content-color-scheme: light;
}

:root[data-theme="dark"] {
    --aura-content-color-scheme: dark;
}

Would this approach potentially cause conflicts with Vaadin’s own theme handling when using data-theme like this?

Or is using data-theme for the framework-agnostic theme state and only using Aura-specific variables inside the adapter stylesheet considered a reasonable approach?

Does it specifically need to be called theme for Aura/Vaadin integration?

Another question:
Is there a Vaadin-specific way to determine the initial effective color scheme in order to initially persist a user-specific preference in the database?

The idea would be:

  • initially use the browser/system preference
  • persist that as the initial user preference
  • later allow the user to override it independently from system/browser defaults

Currently I would probably use something like this:

UI.getCurrent().getPage().executeJs(
    """
    return document.documentElement.getAttribute('data-theme');
    """
).then(String.class, theme -> {
    // [...] persist theme preference for user
});

And just to clarify the original use case again because that question came up earlier:

One of the main reasons for this setup is that I would like to force certain areas such as the navbar or drawer into a fixed dark color scheme without redefining all dark theme values again manually in CSS.

That would otherwise become error-prone once the base theme changes.

Another possible use case (even though I personally do not currently use this in my projects) would be theme previews for users.

For example:

  • enterprise/business applications that allow users to choose custom themes
  • developer documentation/showcases demonstrating how the theme behaves in different variants

This shouldn’t conflict with Vaadin in any way. The only thing Flow does, when you define a @ColorScheme is set the native CSS color-scheme property on the html element (and with Lumo, set the <html theme="dark"> attribute for backwards compatibility).

Your approach seems reasonable to me.

No, it doesn’t.

Not really. And you don’t really need to do anything else except set the color-scheme to light dark.

html {
  color-scheme: light dark;
}

This makes Aura follow the user preference automatically. Then, to allow the user to override that for you app, add a control (select, radio group – your choice) that sets the <html data-theme> attribute accordingly: either data-theme="light" or data-theme="dark" to override the OS settings, or remove the attribute to follow the OS setting again. You don’t need to know the initial effective color scheme in your application code.

With Aura, to have this setup, set the global/page color scheme to dark, and the content color scheme to the user preference:

styles.css

:root {
  color-scheme: dark;
  --aura-content-color-scheme: light dark;
}

:root[data-theme="light"] {
    --aura-content-color-scheme: light;
}

:root[data-theme="dark"] {
    --aura-content-color-scheme: dark;
}

I assume this means the user can choose a different theme that changes colors, fonts, and possibly sizing/density? That’s easy to do with additional stylesheets that you add/remove at runtime with Page.addStyleSheet().

I’m not entirely sure what you mean by “demonstrating how the theme behaves in different variants”. Can you elaborate on that?

1 Like

“Not really. And you don’t really need to do anything else except set the color-scheme to light dark.”

“This makes Aura follow the user preference automatically. Then, to allow the user to override that for your app, add a control (select, radio group – your choice) that sets the <html data-theme> attribute accordingly: either data-theme="light" or data-theme="dark" to override the OS settings, or remove the attribute to follow the OS setting again. You don’t need to know the initial effective color scheme in your application code.”

But how would I initialize the value of the control that allows the user to switch the theme?

If I understand correctly, the application itself does not need to know the effective initial color scheme, but the UI control still somehow needs to reflect the currently active state initially.

“With Aura, to have this setup, set the global/page color scheme to dark, and the content color scheme to the user preference:”

If I want either the drawer OR the navbar to have a fixed color scheme, but not both at the same time, then the global approach would not really work for that use case, unless I misunderstood the recommendation.

Additionally, when setting dark mode globally, the surrounding area around the content also becomes dark, which in my case is an unwanted side effect.

“I’m not entirely sure what you mean by “demonstrating how the theme behaves in different variants”. Can you elaborate on that?”

As mentioned earlier, I currently do not use either of those scenarios myself, and they may very well be unnecessary because at the moment I only need this for the navbar/drawer use case mentioned above.

Those examples were mainly intended as possible scenarios where I could imagine it being useful to independently switch the color scheme for specific layout areas.

There are probably better examples, and maybe these are edge cases where it is ultimately simpler to manually apply the desired styling instead.

Just to better illustrate the idea, here are a few rough examples (I’m not even sure how useful these actually are in practice):

Theme / Light-Dark mode selection

Internal developer/design-system documentation explaining how theming should be applied

Pricing overview with visual highlighting of the “recommended” or “best” offer

So maybe this is not really the correct approach for these kinds of cases.

Regardless of that, the explanation about color-scheme and how aura-accent-color interacts with all of this already helped me solve my navbar/drawer issue, so thanks again for that.

Edit: The last use case I mentioned doesn’t make sense, since the highlight obviously also needs to be visible in dark theme.

If the user hasn’t made a choice (nothing stored in db or localStorage), it is the same as if they’d chosen “System” or “Auto”. So you initialize the UI control with the “System” or “Auto” option selected.

If they then choose “Dark”, you store that choice somehow in your app (db or browser localStorage). Then on the next visit, you read the value from there, and initialize the UI control accordingly.

Btw, you can also use the Page.setColorScheme() method in Flow instead of adding/removing an attribute on the <html> element.

At the moment, Aura doesn’t actually support setting the color scheme on the drawer or navbar specifically. That’s something we might want to add as an option. I.e., this doesn’t work as expected:

vaadin-app-layout::part(drawer) {
  color-scheme: dark;
  background: var(--aura-background-color);
}

You can probably make it work for your app by overriding any problematic colors explicitly, but I don’t think it’s a long-term solution.

Perhaps a more viable workaround is to wrap all content in the drawer or navbar with a div and apply the aura-accent-color class name on it, and then apply color-scheme: dark on that element and set the background color.

If you make the drawer or navbar dark while the content area is light, do you still want to keep empty area around the content, which is controlled with --aura-app-layout-inset? I’m having a hard time visualizing how that would look nice if that is not set to zero, as well as --aura-app-layout-radius.

Thanks, that helped. Yeah, those should be doable with what we’ve discussed here, by applying the .dark class in your styles.css example. You can add a similar .light class as well, so that you can show how a light theme looks if the app is using the dark theme.

1 Like

“If the user hasn’t made a choice (nothing stored in db or localStorage), it is the same as if they’d chosen “System” or “Auto”. So you initialize the UI control with the “System” or “Auto” option selected.”

That makes sense. I honestly did not think about the “System/Auto” state as an explicit option. That was my mistake.

“You can probably make it work for your app by overriding any problematic colors explicitly, but I don’t think it’s a long-term solution.”

At the moment, I override it roughly like this:

vaadin-app-layout::part(drawer) {
    background: light-dark(
        var(--my-background-light),
        var(--my-background-dark)
    );

    color: light-dark(
        var(--my-text-light),
        var(--my-text-dark)
    );
}

vaadin-app-layout::part(navbar) {
    color-scheme: dark;
    background: var(--aura-background-color);
    color: var(--vaadin-text-color);
}

But if this is not considered a viable long-term solution, I will probably rethink this approach.

“If you make the drawer or navbar dark while the content area is light, do you still want to keep empty area around the content”

No, you are absolutely right. I am currently using:

--aura-app-layout-inset: 0px;

I probably remembered an earlier incorrect state or some side effect from when I was experimenting with different approaches while searching for a solution.