color-mix()
function (see Can I use). Baselayer 3.3.x uses this function to set up a range of shades for its built-in (example) colors.
Example using background bg-*
utility classes:
bg-blue
bg-green
bg-amber
bg-red
bg-gray
Rationale for Baselayer’s color system
I wanted to develop a color system that was based on CSS variables for generating a series of shades for each color, so that the stylesheet didn’t need to be overloaded with all the shade permutations for each color — most of which you would never use.
Previously, I experimented with setting shades based on lightness channels, first in the hsl
(Baselayer 3.1.x) and later in the new oklch
color systems (Baselayer 3.2.x). This was successful, but it involved the additional effort of converting colors to OKLCH and to separate out their color channels into different variables, for enabling them to work with the shade channel utilities.
In Baselayer 3.3, I have switched to setting shades based on mixing in white or black using the new color-mix()
function (see MDN docs: color-mix()). The CSS classes remain the same as before, while the color format is now hex (#) based. This is easier to maintain, and stylesheet is smaller.
Color and shade utility classes
All colors and shades are declared in variables.css
.
Color utility classes (declared in @layer color-setup
) are prefixed acording to where the color will be applied:
- Border color:
b-*
hover:b-*
— border color on hover
- Text color:
t-*
hover:t-*
— text color on hover
- Background color:
bg-*
hover:bg-*
— background color on hover
I have named the colors according to their common names (blue, green, amber, red, gray), instead of opting to name them according to the common user interface (UI) “success”, “warning”, “danger” etc. — so that you can make color utilities or components with colors dedicated to those purposes, meanwhile allowing you to adjust these built-in named colors and also add your own.
All color utilities “start” at their *-500
level, or mid-tone (this is what you will get if you don’t add a shade modifier utility).
The shade modifier utilities (declared in @layer color-shade
) are as follows:
- Border:
b-100
throughb-900
hover:b-100
throughhover:b-900
- Text:
t-100
throught-900
hover:t-100
throughhover:t-900
- Background:
bg-100
throughbg-900
hover:bg-100
throughhover:bg-900
Also available:
- Dark theme, via the
theme-dark
wrapper — see . b-dark-invert
,t-dark-invert
, andbg-dark-invert
modifiers that invert the shade for dark mode, in those situations when you want light elements in light mode to become dark elements in dark mode (and vise versa) — see darker and inverted shade utilities.- Black, white, reversi, and transparent — see other Baselayer color utilities.
Example border, text and background utilities:
<div class="b-3 b-green"></div>
<div class="t-green t-600 t-dark-invert"></div>
<div class="t-black bg-green bg-300"></div>
Example usage:
<div aria-label="Note" class="popout mb-2 bl-3 b-amber b-300 p-2 t-black bg-amber bg-100">
⚠ Warning alert panel.
</div>
The shades *-100
through *-900
, if used alone, don’t provide color. But if you use them to supplement one of the other colors above, then that color class will provide the color, and the shade class will set its lightness level.
Colors and accessibility
In your text and background color combinations, be careful to ensure that the text is readable — there needs to be an adequate contrast. Generally, you will want to aim at WCAG level AA for accessibility compliance.
For WCAG level AA compliance, most user interface colors need to be darker than the middle shade (i.e. use *-600
up) if the text color is white, or lighter than the middle shade (i.e. use *-400
down) if the text color is black.
However, any colors near yellow, such as Baselayer amber, as well as orange and yellow-green (not included) are notoriously difficult for accessibility. You may do better using a lighter background amber and pairing it with black text.
When colorizing buttons, remember to set their hover:
hover state shades too.
<!-- Default button -->
<button type="button" name="button">Button</button>
<!-- Blue button -->
<button class="bg-blue bg-600 hover:bg-700" type="button" name="button">Button</button>
<!-- Amber button -->
<button class="t-black hover:t-black bg-amber bg-400 hover:bg-500" type="button" name="button">Button</button>
<!-- Green outline (a.k.a. ghost) button -->
<button class="b-1 b-green bg-transparent t-green t-600 hover:b-700 hover:t-white hover:bg-green hover:bg-700" type="button" name="button">Button</button>
Background reading on colors and accessibility:
- Useful blog posts from The Accessibility (A11Y) Project:
- Web Content Accessibility Guidelines (WCAG) 2
- Contrast and Color Accessibility (WEB AIM)
- The Coolors contrast checker
- Web Accessibility: Understanding Colors and Luminance (Mozilla Developer Network Docs)
Adding more colors and shades
You can, of course, add any colors you want, and in any format you want.
However, if you want to use Baselayer 3.3.x’s shade classes -100
thorugh -900
on your color(s), then you need to start from a mid-tone that works as a -500
shade. The color-mix()
formulas mix in various levels of white to shades -100
through -400
, and various levels of black to shades -600
through -900
.
Also, you will need to put your color(s) in @layer color-setup {}
so that it gets added before @layer color-shades {}
.
You can add your own project colors in any format, but the Baselayer color-mix()
formulas will output shade in SRGB format.
Dark theme
Baselayer 3 has a simple dark theme built in. The dark theme is as follows:
- HTML elements are generally flipped from light to dark, or dark to light:
- Body background is near black
- Text is near white
- Table borders, horizontal rules, form inputs are dark gray
- Text links are a lighter blue
- Default buttons are a lighter gray
- Color utilities (blue, green, amber, red, gray) are slightly darkened, to make them easier on the eye.
- On hover, the link text color and the default button background color both are made lighter (inverted behavior).
- Color utilities can optionally be inverted by adding the
*-dark-invert
modifier classes.
The theme-dark
class
In baselayer.min.css
the dark theme is set by the CSS class theme-dark
that can be programatically added to the <html>
tag by a JavaScript toggle.
If you don’t want to give your visitors the option to toggle, then you can manually refactor the CSS to make the dark theme simply respond to the prefers-color-scheme: dark
instead.
@media (prefers-color-scheme: dark) {
/*
Your dark theme styles
*/
}
Darker and inverted shade utilities
Each color shade variant has a darker shade for the dark theme. For example, in dark mode bg-100
is slightly darker than in light mode. This makes the shades easier on the eye in dark mode.
There are, however, circumstances in your design where you don’t want colors to be merely darkened but also inverted (light shades become dark shades, and dark shades become light shades).
This shade inverting has been built into the dark theme for (classless) <body>
background, text, links, form inputs, table borders, and horizontal rules.
Baselayer 3 has an optinal inverted version of of its shade utilities (for text, border, and background), as well as having them shoghtly darkened. To make this happen, all you need to do is add another modifier class to those elements you wish to shade invert for dark mode.
Example using bg-blue
:
<!-- will slightly darken in dark mode -->
<div class="bg-blue bg-100"></div>
<!-- will slightly darken AND invert in dark mode -->
<div class="bg-blue bg-100 bg-dark-invert"></div>
Shade (lightness) | bg-blue |
bg-blue bg-dark-invert |
---|---|---|
bg-100 |
||
bg-200 |
||
bg-300 |
||
bg-400 |
||
bg-500 |
||
bg-600 |
||
bg-700 |
||
bg-800 |
||
bg-900 |
The middle t-500
, b-500
, and bg-500
do not invert, of course. So, e.g. bg-500 bg-dark-invert
does not exist in baselayer.css
.
Baselayer 3.3.x’s color shades are darkened for the dark theme
Hover states for links and buttons
In the default light theme, the blue link text becomes a darker blue, and the default gray button becomes a darker gray, on :hover
states. But in the dark theme, this behaviour is inverted: links become a lighter blue and buttons become a lighter gray.
You can easily change this. Lighter and darker options for blue links and gray buttons are included in variables as follows:
.theme-dark {
/* text color for links (default) */
--tc-link: color-mix(in srgb, var(--blue) calc(var(--l400) * 2), white);
--tc-link-lighter: color-mix(in srgb, var(--blue) calc(var(--l300) * 2), white);
--tc-link-darker: color-mix(in srgb, var(--blue) calc(var(--l500) * 2), white);
--tc-link-hover: var(--tc-link-lighter);
/* background color for buttons (default) */
--bgc-btn: color-mix(in srgb, var(--gray) calc((100% - var(--l600)) * 2), black);
--bgc-btn-lighter: color-mix(in srgb, var(--gray) calc((100% - var(--l500)) * 2), white);
--bgc-btn-darker: color-mix(in srgb, var(--gray) calc((100% - var(--l700)) * 2), black);
--bgc-btn-hover: var(--bgc-btn-lighter);
}
To make your styled links and buttons have an inverted behavior as above, you need to use *-dark-invert
utilities.
Dark theme HTML body background color
Baselayer 3’s dark theme <body>
color has been made darker than the color shades set by the bg-900
utility, so that elements colored by those shades are still visible. For this purpose an shade variable of --l1000
has been added. (Therefore, you can use bg-900
or bg-100 bg-dark-invert
to color the background of a panel, without it “disappearing” into the body background color.)
.theme-dark {
--l1000: 87.5%;
--bgc-body: color-mix(in srgb, var(--gray) calc((100% - var(--l1000)) * 2), black); /* = #1e1e1e */
}
As with all Baselayer’s variables, this dark theme <body>
color is an example that you can change to suit the needs of your project.
Dark/light theme toggle
Baselayer’s dark/light theme switcher JavaScript uses sessionStorage in the user’s browser to remember their theme preference as they visit multiple pages in this documentation.
This script adds the CSS class theme-dark
to the <html>
tag of the webpage, so that it is implimented (almost) immediately, before the <body>
is painted in the browser window.
The switcher script also adds theme-light
to the <html>
tag in light mode, but there are no theme-light
classes in baselayer.min.css
. The theme-light
class is merely there for visual confirmation to the deleveloper viewing the browser inspector, and it is used for adjusting the dark/light theme state indicator symbol in the switcher button.
The theme switcher in the Baselayer docs is built into its switcher.js
demo toggling system, that powers the buttons in the sidebar. If you want to use the same dark/light mode toggler, here it is isolated below (no JS framework required):
const matchMediaDark = window.matchMedia('(prefers-color-scheme: dark)');
const htmlClassList = document.querySelector('html').classList;
function themeDark() {
sessionStorage.setItem("baselayerTheme", "dark");
htmlClassList.remove('theme-light');
htmlClassList.add('theme-dark');
}
function themeLight() {
sessionStorage.setItem("baselayerTheme", "light");
htmlClassList.remove('theme-dark');
htmlClassList.add('theme-light');
}
function toggleTheme() {
if ( sessionStorage.baselayerTheme === 'dark' ) {
themeLight();
} else {
themeDark();
}
};
function baselayerInit() {
if (sessionStorage.baselayerTheme === 'dark' || (!('baselayerTheme' in sessionStorage) && matchMediaDark.matches)) {
themeDark();
} else {
themeLight();
}
};
baselayerInit();
You will also need a toggle button, like the one in the sidebar. The checkmarks are added in by CSS pseudo-selectors.
Another, simpler example theme toggle button, using glyphs selected from &what;
<style>
.theme-dark .theme-toggle span::before { content: '☀️ light '; }
.theme-light .theme-toggle span::before { content: '🌙 dark '; }
</style>
<button class="theme-toggle pl-1 t-reversi-alt bg-reversi" onclick="toggleTheme()">
<span>theme</span>
</button>
The simple example above uses bg-reversi
to put a “night time” black background behind the moon and a “day time” white background behind the sun, meanwhile t-reversi-
flips the text color the opposite way. See black, white, reversi, and reversi-alt below.
Other Baselayer color utilities
Other color utilities included in Baselayer cover black, white, and transparent, as follows:
Black, white, reversi, and reversi-alt
There are utilities for border color, text color, and background color for each of the following (and hover:
prefix states):
-black
— named color black:-white
— named color white:-reversi
is black on a light theme, white on a dark theme:-reversi-alt
is white on a light theme, black on a dark theme:
<div class="t-white bg-black">Black</div>
<div class="t-black bg-white">White</div>
<div class="t-reversi-alt bg-reversi">Reversi</div>
<div class="t-reversi bg-reversi-alt">Reversi-alt</div>
You don’t need to use *-dark-invert
on the reversi and reversi-alt utilities. And *-dark-invert
doesn’t work on the black and white utilities.
Transparent background
E.g. for outline buttons.
- Transparent:
bg-transparent
There are no hover states of bg-transparent
.