Menu

Color modes

The theme script switches between light, dark and auto color modes, plus the base gray, font, primary color, corner radius and the page layout, without a page reload.

Overview

Tabler includes a small script, tabler-theme.js, that controls the settings below. Each setting is read from a URL query parameter or localStorage, then applied as a data-bs-* attribute on <html>. Tabler's CSS reads these attributes to switch styles instantly, with no page reload.

Setting Attribute Default Allowed values
Color mode data-bs-theme auto light, dark, auto
Base gray shade data-bs-theme-base neutral slate, gray, zinc, neutral, stone, mist
Font family data-bs-theme-font sans-serif sans-serif, serif, monospace, comic
Primary color data-bs-theme-primary blue blue, azure, indigo, purple, pink, red, orange, yellow, lime, green, teal, cyan, inverted
Corner radius data-bs-theme-radius 1 0, 0.5, 1, 1.5, 2
Navigation position data-bs-navbar-position horizontal horizontal, vertical, combined
Container width data-bs-layout default default, fluid, boxed
Navbar behavior data-bs-navbar default default, sticky
Navigation theme data-bs-navbar-theme default default, dark, primary
Sidebar data-bs-sidebar default default, folded, folded-hover

The data-bs-theme-base colors are the gray palettes that back every other color mode. See Theme base colors for every shade of every palette; this page only covers the script that switches between them.

The four data-bs-theme-font values are also available as utilities - .font-sans-serif, .font-serif, .font-monospace and .font-comic - to set a font family on a single element regardless of the page-wide setting.

Each setting only appears on <html> when it differs from its default. For example, data-bs-theme-primary="blue" is never written because blue is already the default - the browser falls back to Tabler's built-in styles instead.

Layout settings

The last five settings change the page layout rather than its colors, and they need the markup described in Page layouts:

  • data-bs-navbar-position only does something on a page that renders both navigations - a vertical aside.navbar-vertical and a horizontal navbar, both direct children of .page. The CSS shows one and hides the other, and combined keeps both on screen. A page with a single navbar keeps it, whatever the stored value says, so a stale choice can never leave a page with no navigation.
  • data-bs-layout is the attribute form of the .layout-fluid and .layout-boxed body classes. Use one route or the other, not both.
  • data-bs-navbar="sticky" makes the top navbar stick to the top of the viewport while the page scrolls. On the two-row navbar only the top row sticks.
  • data-bs-navbar-theme colors whichever navigation the page shows, since the vertical sidebar is a navbar too. default means "follow the page color mode", not "always light". dark gives it the dark palette on a light page - the same result as putting data-bs-theme="dark" on the element in your markup. primary paints it in the primary color with the dark text palette.
  • data-bs-sidebar="folded" folds every vertical navbar on the page into a narrow, icon-only rail. folded-hover starts folded too, but unfolds when the user hovers over it or moves keyboard focus into it. See Folded sidebar for the markup and the tabler:sidebar-folded event.

Setup

Load the theme script right after the opening <body> tag, and do not add defer or async to it:

<body>
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/js/tabler-theme.min.js" integrity="sha384-A6W4zH+ZwbIRoGo4iSD4D4WDDcIo33A113EHIzBSCq4kKbqSxvOkky/U1QBJAe8/" crossorigin="anonymous"></script>
...
</body>

The script has to run and set data-bs-theme on <html> before the browser paints anything. If you defer it, or place it in <head>, the page paints with the light theme first and then flips to dark a moment later - a visible flash of the wrong theme (FOUC). Loading it inline, first thing in <body>, is what prevents that flash.

This is separate from Tabler's main tabler.min.js bundle, which still loads with defer near the end of <body> as usual.

Color mode: light, dark, and auto

auto is the default: with no stored choice, Tabler follows the visitor's operating system setting. Set theme to light or dark to force a mode instead:

<!-- Force dark mode -->
<html data-bs-theme="dark">

<!-- Follow the OS setting -->
<html data-bs-theme="auto">

With auto, the script checks the prefers-color-scheme media query once on load, and resolves it to light or dark right away - data-bs-theme is set to the resolved value, never to the literal string auto. It also keeps listening for OS-level changes: if the visitor switches their system between light and dark while your page is open, the script updates data-bs-theme on the fly, with no reload needed.

Setting values

You can set any of these keys in three ways, and they all agree with each other:

  1. URL query parameter - add ?theme=dark (or any other key) to the page URL. The script reads it, applies it, and saves it to localStorage so it persists on the next visit.
  2. localStorage - each key is stored under tabler-<key>, for example tabler-theme or tabler-theme-primary. This is what makes the choice persist across page loads once it has been set once, by either method here.
  3. Server-rendered attribute - if you already know the visitor's preference (from a cookie or account setting), render the data-bs-* attributes on <html> yourself. The script keeps them as the starting value and only replaces them with a stored choice or a URL parameter.

A query parameter always wins over what's already stored, and updates the stored value for next time:

https://example.com/?theme=dark&theme-primary=azure&navbar-position=vertical&layout=boxed

Building your own theme switcher

Tabler's own demo pages ship two working examples you can copy:

  • shared/components/navbar/NavbarSideTheme.astro is a simple light/dark toggle in the navbar. It's just two links:

    <a href="?theme=dark">Enable dark mode</a>
    <a href="?theme=light">Enable light mode</a>

    Since the theme script reads theme straight from the query string, a plain link is enough to switch modes - no JavaScript of your own required. .hide-theme-dark and .hide-theme-light are used to show only the relevant link for the current mode.

  • shared/components/demo/ThemeSettings.astro is a full settings panel, opened from a floating button, with radios and color swatches for every key. It only builds the form markup - the behavior lives in a page-level script in shared/layouts/BaseLayout.astro that, on every change event:

    document.documentElement.setAttribute('data-bs-' + key, value)
    window.localStorage.setItem('tabler-' + key, value)
    url.searchParams.set(key, value)
    window.history.pushState({}, '', url)

    It sets the attribute immediately, saves it to localStorage, and pushes it into the URL without a reload, so a shared link reproduces the same look. Its "Color scheme" tiles are presets: one tile sets theme and navbar-theme together, and reads as selected only while both keys hold the tile's values. The script knows nothing about presets - they are a convenience of the panel, built on the keys above. Use this as a starting point for your own settings UI - it's meant to be copied and adjusted, not used as-is in production.

Resetting the choice

To reset a key back to its default, remove its localStorage entry and its data-bs-* attribute:

document.documentElement.removeAttribute('data-bs-theme')
window.localStorage.removeItem('tabler-theme')

ThemeSettings.astro's "Reset changes" button does this for every key at once, and also strips them from the URL's query string.

Accessibility

auto mode respects the visitor's OS-level prefers-color-scheme setting instead of forcing a choice on them, which is the accessible default when you don't have a stronger reason to pick one. If you do force light or dark, still offer a visible way to switch modes rather than only reading the OS preference once - some visitors change their preference based on time of day or lighting conditions.

Switching theme-primary, theme-base, or theme-radius can change color contrast across the page. Re-check contrast for any custom combination you offer, since some primary colors don't clear WCAG AA contrast with white text at their default shade.