
Flexible and unstyled theming system that supports multiple theme attributes with persistent storage and automatic UI synchronization.
- π¨ Multiple theme attributes support (color schemes, modes, layouts, etc.)
- πΎ Automatic localStorage persistence
- π UI state synchronization across controls
- π― Data attribute-based targeting
- π± Support for various control types (select, button, checkbox, radio and range)
- π Zero dependencies
- π¦ CommonJS & ESM support
npm install @netoum/themex
import { Themex } from '@netoum/themex';
const options = [
{
key: 'theme',
default: 'gray',
values: ['gray', 'red']
},
{
key: 'mode',
default: 'light',
values: ['light', 'dark']
},
{
key: 'density',
default: 'compact',
values: ['compact', 'wide']
},
{
key: 'size',
default: '2',
values: ['1', '2', '3']
}
];
new Themex(options);
data-${key}
: Applied to HTML document (for ex: data-mode="dark")data-themex-key
: Identifies the theme attribute to modifydata-themex-value
: Specifies the value to apply
aria-current
: Applied to Button Set controls to indicate current selectionaria-pressed
: Applied to Button Toggle controls to indicate current selectiondata-selected
: Applied to Select Options controls to indicate current selection
Themex works with various HTML controls:
You must add "set" to each button The selected button will have "aria-current="true" attribute
<button data-themex-key="mode" data-themex-value="light" set>
Light
</button>
<button data-themex-key="mode" data-themex-value="dark" set>
Dark
</button>
<div role="button" data-themex-key="mode" data-themex-value="light" set>
Light
</div>
<div role="button" data-themex-key="mode" data-themex-value="dark" set>
Dark
</div>
button[aria-current="true"] {
background-color: var(--color-primary-contrast);
color: var(--color-primary);
}
div[role="button"][aria-current="true"] {
background-color: var(--color-primary-contrast);
color: var(--color-primary);
}
You must add "toggle" to each button The selected button will have "aria-pressed="true" attribute
<button data-themex-key="mode" data-themex-value="light" toggle>
Light
</button>
<button data-themex-key="mode" data-themex-value="dark" toggle>
Dark
</button>
<div role="button" data-themex-key="mode" data-themex-value="light" toggle>
Light
</div>
<div role="button" data-themex-key="mode" data-themex-value="dark" toggle>
Dark
</div>
button[aria-pressed="true"] {
display: hidden
}
div[role="button"][aria-pressed="true"] {
display: hidden
}
By default Select do not have a common way to track the selected option on all browser, so we add "data-selected="true" attribute for styling
<select data-themex-key="theme">
<option value="gray">Gray</option>
<option value="red">Red</option>
</select>
option[data-selected="true"] {
background-color: var(--color-primary-contrast);
color: var(--color-primary);
}
You must select 2 values, the first one being the value selectd when the checkob is checked, the second beign the fallback when the checkbox is unchecked
<label>
Wide
</label>
<input type="checkbox"
data-themex-key="density"
data-themex-value="wide,compact">
<label>
Gray Theme
</label>
<input type="radio" name="theme-radio" data-themex-key="theme" data-themex-value="gray">
<label>
Red Theme
</label>
<input type="radio" name="theme-radio" data-themex-key="theme" data-themex-value="red">
:root {
/* Theme */
--color-primary: var(--theme-color-primary);
--color-primary-contrast: var(--theme-color-primary-contrast);
/* Theme/Mode */
--color-body: var(--theme-color-body);
--color-body-contrast: var(--theme-color-body-contrast);
/* Density */
--spacing: var(--density-spacing);
/* Base */
--color-gray-1: #f6f6f6;
--color-gray-2: #e2e2e2;
--color-gray-3: #8b8b8b;
--color-gray-4: #6f6f6f;
--color-gray-5: #3e3e3e;
--color-gray-6: #222222;
--color-red-1: #fff8f6;
--color-red-2: #ffddd8;
--color-red-3: #ff4647;
--color-red-4: #e0002b;
--color-red-5: #830014;
--color-red-6: #530003;
}
/* Theme variations */
[data-theme="gray"] {
--theme-color-primary: var(--color-gray-2);
--theme-color-primary-contrast: var(--color-gray-6);
}
[data-theme="red"] {
--theme-color-primary: var(--color-red-2);
--theme-color-primary-contrast: var(--color-red-6);
}
/* Theme/Mode variations */
[data-theme="gray"][data-mode="light"] {
--theme-color-body: var(--color-gray-2);
--theme-color-body-contrast: var(--color-gray-6);
}
[data-theme="gray"][data-mode="dark"] {
--theme-color-body: var(--color-gray-6);
--theme-color-body-contrast: var(--color-gray-2);
}
[data-theme="red"][data-mode="light"] {
--theme-color-body: var(--color-red-2);
--theme-color-body-contrast: var(--color-red-6);
}
[data-theme="red"][data-mode="dark"] {
--theme-color-body: var(--color-red-6);
--theme-color-body-contrast: var(--color-red-2);
}
/* Density variations */
[data-density="compact"] {
--spacing: 0.5rem;
--font-size: 0.875rem;
}
[data-density="wide"] {
--spacing: 1rem;
--font-size: 1rem;
}
/* See it in action */
body {
background-color: var(--theme-color-body);
color: var(--theme-color-body-contrast);
padding: var(--spacing);
font-size: var(--font-size);
}
button[aria-current="true"] {
text-decoration: underline;
}
You will find some Themex examples with Vite and Eleventy using more complex theming and referencing. Some example also uses the System mode for Light/Dark mode
MIT
The whole project, including the examples are fully open source and brought to you by Netoum.com