How it works
Four inputs, a share URL, and a small API if you would rather drive it yourself.
Options
The four config keys are the whole input surface. Everything else — every color token for both themes — is derived from them.
| Key | Values | What it does |
|---|---|---|
accent |
any hex | Your brand color. Everything in the accent family is derived from it, separately for each theme. Default #0073ea. |
neutral |
neutral · cool · warm · accent |
The hue the grays lean toward. neutral is a pure gray; cool and warm add a slight blue or yellow cast; accent tints them with your own hue. |
radius |
0–20 |
Corner radius in pixels. Sets --radius and derives --radius-sm and --radius-lg from it. |
density |
compact · cozy · comfortable |
Scales the space scale and control heights — 0.75×, 1× or 1.25×. Type sizes are not touched. |
The _78.gen API
The generator is a normal part of the kit, in js/generator.js. Load
it alongside _78.js and you can derive themes in your own product
— a per-workspace accent, a customer-branded portal.
<script defer src="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/js/_78.js"></script>
<script defer src="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/js/generator.js"></script>
// Derive — config in, both token sets out
var result = _78.gen.derive({
accent: '#7c5cff', neutral: 'cool', radius: 10, density: 'cozy'
});
// result = { config, accent, light, dark, base, notes }
// Output
_78.gen.css(result); // the full paste-ready stylesheet
_78.gen.css(result, { base: false }); // colors only, no radius/density block
// Apply to the live page, and persist it
_78.gen.apply(result); // writes a <style> and saves config + css to localStorage
_78.gen.clear(); // remove it and go back to the kit default
_78.gen.stored(); // the saved config, or null
// Contrast
_78.gen.report(result.light, 'light');
// [{ label, ratio, need, large, soft, pass }, …]
// Sharing
_78.gen.encode(result.config); // '7c5cff.cool.10.cozy'
_78.gen.decode('7c5cff.cool.10.cozy'); // → a config object
_78.gen.fromUrl(); // read ?theme= from the current URL
_78.gen.shareUrl(result.config); // the full URL with ?theme= set
// Preview one theme on a container, without touching the page
_78.gen.preview(el, result.light, result.base);
| Member | What it does |
|---|---|
derive(config) | The whole job. Returns { config, accent, light, dark, base, notes }; notes lists any lightness nudged to pass contrast. |
css(result, opts) | Paste-ready stylesheet. { base: false } omits the shape and density block. |
block(sel, tokens, grouped) | One CSS rule, if you want to assemble output yourself. |
apply(result) | Applies as a real stylesheet — both theme blocks, so the theme toggle keeps working — and saves it. Fires _78:genchange. |
clear() | Removes the stylesheet and the saved theme. |
stored() | The saved config, or null. |
preview(el, tokens, base) | Writes tokens onto one element as inline custom properties — how the two panes above show both themes at once. |
report(tokens, mode) | The contrast rows for a derived theme. |
encode · decode · fromUrl · shareUrl | The ?theme= round trip. |
DEFAULTS · STORAGE | The default config, and the localStorage key (_78-gen). |
PREPAINT | The pre-paint snippet, as a string — see below. |
Restoring a saved theme before first paint
apply() stores the generated CSS next to the config, so it can be put back with no
re-derivation. This snippet goes after the theme snippet and before any stylesheet — it is
what stops an applied theme flashing the default on every navigation. This site runs it on every
page, which is why Apply above survives a reload.
<script>
(function(){try{var s=JSON.parse(localStorage.getItem('_78-gen')||'null');if(!s||!s.css)return;
var e=document.createElement('style');e.id='_78-gen-style';e.textContent=s.css;
document.documentElement.appendChild(e);document.documentElement.dataset._78Gen='on';}catch(e){}})();
</script>
See Theming for the token contract the generator writes to, and Getting started for the theme snippet that goes beside this one.