Just want to see it in action?
Live theming demo using open-props's custom properties to theme AgnosticUI 💪🏽 💥
Theming AgnosticUI
AgnosticUI uses CSS custom properties to allow for a customized look and feel. These custom properties are prefixed with --agnostic-
which helps to prevent any potential naming collisions with other stylesheets.
Color Schemes
AgnosticUI supports light and dark color schemes based on the user's system preferences by default. However, you'll notice in the linked colors.css
that we do this by toggling between two variants of the same color based on whether we've detected light or dark mode respectively. A simplified example of how this work follows:
/* First we define values for light and dark modes */
--agnostic-primary-modelight: /* some color value */
--agnostic-primary-modedark: /* some color value */
Later, we will assign these values to --agnostic-primary
based on currently detected color scheme:
/* By default, light mode is assumed */
--agnostic-primary: var(--agnostic-primary-modelight);
/* But, if user's set their OS to prefer dark, we instead use that */
@media (prefers-color-scheme: dark) {
--agnostic-primary: var(--agnostic-primary-modedark);
}
Example of Theming with CSS Custom Properties
Here's an example of how you might override AgnosticUI's default CSS custom properties to reflect your brand's primary
colors for both light and dark schemes. We also apply a Google font:
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap');
:root {
--agnostic-font-family: font-family: 'Roboto', sans-serif;
/* In light mode we willl have white text against a dark green background. */
--agnostic-light-modelight: white;
--agnostic-primary-modelight: #053337;
--agnostic-primary-hover-modelight: #0a474c;
/* In dark mode we'll invert the text color and use a much lighter shade of green */
--agnostic-primary-modedark: #12adba;
--agnostic-light-modedark: black;
--agnostic-primary-hover-modedark: #1ec3d1;
}
The above setup will allow --agnostic-primary
and --agnostic-light
to be set in a color scheme compatible way. As such, be sure to set both the -modelight
and -modedark
variants for each color you plan to override so you don't unintentionally break color scheme toggling.
CSS Custom Properties
AgnosticUI's common.min.css
contains all of the CSS custom properties, but you can alternatively import the custom properties as an individual module from agnostic-css/public/css-dist/common.properties.min.css
Colors
Here is a full list of the available color tokens set for light and dark colors schemes (at time of writing):
:root {
[color-scheme="light"] {
color-scheme: light;
--agnostic-warning-border-accent: var(--agnostic-warning-border-accent-modelight);
--agnostic-warning-border: var(--agnostic-warning-border-modelight);
--agnostic-warning-light: var(--agnostic-warning-light-modelight);
--agnostic-warning-dark: var(--agnostic-warning-dark-modelight);
--agnostic-secondary-hover: var(--agnostic-secondary-hover-modelight);
--agnostic-secondary: var(--agnostic-secondary-modelight);
--agnostic-primary-light: var(--agnostic-primary-light-modelight);
--agnostic-primary-dark: var(--agnostic-primary-dark-modelight);
--agnostic-primary-border: var(--agnostic-primary-border-modelight);
--agnostic-primary-hover: var(--agnostic-primary-hover-modelight);
--agnostic-primary: var(--agnostic-primary-modelight);
--agnostic-light: var(--agnostic-light-modelight);
--agnostic-dark: var(--agnostic-dark-modelight);
--agnostic-gray-dark: var(--agnostic-gray-dark-modelight);
--agnostic-gray-mid-dark: var(--agnostic-gray-mid-dark-modelight);
--agnostic-gray-mid: var(--agnostic-gray-mid-modelight);
--agnostic-gray-light: var(--agnostic-gray-light-modelight);
--agnostic-gray-extra-light: var(--agnostic-gray-extra-light-modelight);
--agnostic-error: var(--agnostic-error-modelight);
--agnostic-error-light: var(--agnostic-error-light-modelight);
--agnostic-error-dark: var(--agnostic-error-dark-modelight);
--agnostic-error-border: var(--agnostic-error-border-modelight);
--agnostic-disabled-color: var(--agnostic-gray-dark-modelight);
--agnostic-disabled-bg: var(--agnostic-gray-light-modelight);
--agnostic-action-border: var(--agnostic-action-border-modelight);
--agnostic-action-light: var(--agnostic-action-light-modelight);
--agnostic-action-dark: var(--agnostic-action-dark-modelight);
--agnostic-action-hover: var(--agnostic-action-hover-modelight);
--agnostic-action: var(--agnostic-action-modelight);
}
[color-scheme="dark"] {
color-scheme: dark;
--agnostic-warning-border-accent: var(--agnostic-warning-border-accent-modedark);
--agnostic-warning-border: var(--agnostic-warning-border-modedark);
--agnostic-warning-light: var(--agnostic-warning-light-modedark);
--agnostic-warning-dark: var(--agnostic-warning-dark-modedark);
--agnostic-secondary-hover: var(--agnostic-secondary-hover-modedark);
--agnostic-secondary: var(--agnostic-secondary-modedark);
--agnostic-primary-light: var(--agnostic-primary-light-modedark);
--agnostic-primary-dark: var(--agnostic-primary-dark-modedark);
--agnostic-primary-border: var(--agnostic-primary-border-modedark);
--agnostic-primary-hover: var(--agnostic-primary-hover-modedark);
--agnostic-primary: var(--agnostic-primary-modedark);
--agnostic-light: var(--agnostic-light-modedark);
--agnostic-dark: var(--agnostic-dark-modedark);
--agnostic-gray-dark: var(--agnostic-gray-dark-modedark);
--agnostic-gray-mid-dark: var(--agnostic-gray-mid-dark-modedark);
--agnostic-gray-mid: var(--agnostic-gray-mid-modedark);
--agnostic-gray-light: var(--agnostic-gray-light-modedark);
--agnostic-gray-extra-light: var(--agnostic-gray-extra-light-modedark);
--agnostic-error: var(--agnostic-error-modedark);
--agnostic-error-light: var(--agnostic-error-light-modedark);
--agnostic-error-dark: var(--agnostic-error-dark-modedark);
--agnostic-error-border: var(--agnostic-error-border-modedark);
--agnostic-disabled-color: var(--agnostic-gray-dark-modedark);
--agnostic-disabled-bg: var(--agnostic-gray-light-modedark);
--agnostic-action-border: var(--agnostic-action-border-modedark);
--agnostic-action-light: var(--agnostic-action-light-modedark);
--agnostic-action-dark: var(--agnostic-action-dark-modedark);
--agnostic-action-hover: var(--agnostic-action-hover-modedark);
--agnostic-action: var(--agnostic-action-modedark);
}
The GitHub-hosted colors.css on master
is the most up-to-date source of truth. AgnosticUI derives these CSS custom properties colors from its colors design tokens.
Color Mode Toggling
AgnosticUI supports light or dark color schemes based on the user's system preferences by leveraging the prefers-color-scheme media query.
If you'd like to additionally support storing user preferences in localStorage
, you will want to set up a color scheme toggle button. To do this, you will need to add a small script at the top of your HTML page in your <head>
so that the color mode is determined as early as possible. This is done to avoid page flashing.
There are many ways you may set this up, but here is one example approach. This script is setup to work in a Vite environment (in our case we've used this script on an Astro site). As such, we have utilized the import.
guards to ensure we're not attempting to access localStorage
when we're server-side rendering. Put differently, the guarded code will only execute when being ran on the client:
<html>
<head>
<!-- ...boilerplate <head> codes omitted for brevity -->
<script>
const STORAGE_KEY = "ag-color-scheme";
// First checks localStorage then system preferences
const getColorScheme = () => {
// Only run this if NOT in SSR mode aka on the client
if (!import.meta.env.SSR) {
// If we have stored the user's preferences we will prefer that
if (localStorage.getItem(STORAGE_KEY)) {
return localStorage.getItem(STORAGE_KEY);
} else {
// Otherwise, we check their OS settings
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
}
};
const setStoredColorScheme = (colorScheme) => {
if (!import.meta.env.SSR) {
localStorage.setItem(STORAGE_KEY, colorScheme);
}
};
const setTheme = (colorScheme) => {
if (!import.meta.env.SSR) {
document.firstElementChild.setAttribute("color-scheme", colorScheme);
}
};
// Sets the theme as early as possible to avoid a flash of incorrect theme
setTheme(getColorScheme());
if (!import.meta.env.SSR) {
document.addEventListener('DOMContentLoaded', () => {
setTheme(getColorScheme());
requestAnimationFrame(() => {
// Of course, these could also be SVGs or text labels if you prefer
let initial = "🔆";
// The main mechanism is an attribute `color-scheme` placed on <html>
if (
document.firstElementChild.hasAttribute("color-scheme") &&
document.firstElementChild.getAttribute("color-scheme") === "light"
) {
// We're in light mode so show toggle to dark icon
initial = "🌙";
}
// Creates a toggle button element
const toggle = document.createElement("button");
toggle.classList.add("item", "toggle");
toggle.textContent = initial;
const toggleHandler = (ev) => {
// Get the current color mode then toggle it and update the store
const currentMode =
document.firstElementChild.getAttribute("color-scheme");
const toggledColorScheme = currentMode === "dark" ? "light" : "dark";
// Set toggled content and set html `color-scheme` attribute. Also, update local storage.
toggle.textContent = toggledColorScheme === "dark" ? "🔆" : "🌙";
setTheme(toggledColorScheme);
setStoredColorScheme(toggledColorScheme);
};
toggle.addEventListener("click", toggleHandler)
// Get navigation links and inject the toggle button at the end
let navBars = document.getElementsByClassName("header-nav");
if (navBars) {
let headerNavBar = navBars[0];
if (headerNavBar) {
headerNavBar.insertAdjacentElement("beforeend", toggle);
}
}
});
});
}
Sizes
AgnosticUI uses the naming convention of: --fluid-N
for rem-based sizes. AgnosticUI represents -N
with pixel-size equivalent (if the root font is defined at 16px
which is normally the case); so --fluid-16
is literally 1rem
, --fluid-40
is literally 2.5rem
and so on. These are derived from the size design tokens.
You can see these in use in the layout docs margins and padding section logical properties.
Typography
The typography design tokens are used to generate typography CSS custom properties which define a light-weight set of type definitions such as: header sizes, the main body font's color, families, etc., and a few other things.
Focus
AgnosticUI uses a particular focus ring which is defined in focus design tokens. As many folks have varying aesthetic preferences for focus rings, you can either override these, or, more drastically redefine the design token values and regenerate.
Reset
AgnosticUI's common.min.css
contains a small reset. If you'd prefer, you can alternatively import the reset as an individual module from agnostic-css/public/css-dist/common.resets.min.css
Other Tokens
The generated CSS custom properties are located in agnostic-css
package's public/css-src directory. These source files are, in fact, what gets concatenated and minified into the dist/common.min.css
global CSS file we ask you to import
.
The common
CSS custom properties are themselves derived from standards-based JSON Design Tokens in a build process that utilizes Style Dictionary. Read more about the design tokens spec here.
See also the Utilities page which goes over available CSS utilities for layout.
Design Tokens
Most of the CSS custom properties in common.min.css
are derived from design tokens using Style Dictionary. Likely, you'll simply want to override these custom properties from within your own scripts as described above. However, it is possible to more aggressively redefine some of the design token values to your liking and then rebuild the properties via the command-line. This might make sense if you're taking an aggressive forking strategy where you use AgnosticUI as a launching point of departure for your own completely customized codebase. Here are the places to look for how we build from design tokens: first look at the design tokens themselves, and then have a look at the build script which simply lets Style Dictionary convert the tokens into CSS custom properties used here.
Be forwarned that this sort of approach is probably not upgrade-safe and you should not change the actual variable names used (just the values), as these variable names are referenced from within the AgnosticUI component codebase.