One framework.
Fifty skins.
The same markup, the same class names, the same JavaScript — wearing a neo-brutalist, glassmorphic, Bauhaus, CRT-terminal, risograph or neon-sign face. Change one attribute, change everything.
Currently wearing Default —
Two lines and you are done
No build step, no config file, no plugin ecosystem to learn.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bukalemun/dist/bukalemun.base.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bukalemun/dist/styles/brutal.min.css">
<script src="https://cdn.jsdelivr.net/npm/bukalemun/dist/bukalemun.min.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bukalemun/dist/bukalemun.min.css">
<script src="https://cdn.jsdelivr.net/npm/bukalemun/dist/bukalemun.min.js" defer></script>
npm install bukalemun
// then
import 'bukalemun/css/base';
import 'bukalemun/styles/brutal.min.css';
import bk from 'bukalemun';
base is the framework — every component, no palette
(30.8 KB gzipped). A skin is the palette on its own (1.7 KB). Swapping skins later
means swapping that second line. The fifty-in-one build exists so a page can let a visitor
switch between them — this site and the demos use it for exactly that — but it costs
91.9 KB gzipped, which is forty-nine palettes you are not using.
Then pick a look:
<html data-bk-style="brutal" data-bk-theme="dark">
Pick a personality
Every card below repaints this entire page. Nothing on the page changes except
<html data-bk-style>.
Density, radius and direction are orthogonal to the skin — they compose with all 50.
One contract, fifty implementations
Components never name a colour. They read semantic tokens; a skin rewrites the tokens. That is the whole trick.
Live palette
These swatches read the current skin's tokens directly.
/* Brand it without touching a component */
:root {
--bk-primary: #ff5c00;
--bk-primary-hover: #e65200;
--bk-on-primary: #ffffff;
--bk-radius-card: 1.25rem;
--bk-font-sans: "Inter", system-ui;
}
/* Or scope it to one region */
.checkout { --bk-primary: #16794b; }
Sixty-plus pieces, all skin-aware
Switch skins while you scroll — every example below follows along.
Buttons
Forms
Feedback
Programmatic, queued, pausable, promise-aware.
Data display
| Project | Owner | Builds | Status | |
|---|---|---|---|---|
| bukalemun | ŞH Şahan | 1284 | Passing | |
| temrensec | AK Ada | 312 | Flaky | |
| maisonask | MK Mert | 97 | Failing | |
| octogent | ZL Zeynep | 2041 | Passing |
Card with media
Hover it. The lift, the shadow and the border all come from the skin.
- 09:12 Commit pushed feat: add 50th skin
- 09:14 Build running Compiling tokens…
- — Deploy Waiting
How does a skin change everything at once?
Can I ship only one skin?
bukalemun.base.min.css plus one file from dist/styles/.Does it need JavaScript?
Overlays
Built on native <dialog>: real top layer, real focus trapping, real Esc.
Intrinsic, not breakpoint soup
Primitives that reflow on their own: stack, cluster, switcher, grid-auto, reel, sidebar.
Fifty not enough?
A skin is just a token document. Move these six dials, watch the whole page follow, then copy the CSS — that file is a skin.
Applies live to this page. Reset restores the active skin.
A runtime, not a framework
Everything is opt-in through data-bk-* attributes, and everything has a programmatic twin.
<button data-bk-open="#dialog">Open</button>
<dialog id="dialog" class="bk-modal">…</dialog>
<table data-bk-table data-bk-filter="#q">
<th data-bk-sort="number">Builds</th>
</table>
<div data-bk-tabs>…</div>
<div data-bk-carousel>…</div>
<span data-bk-counter="1284"></span>
<button data-bk-copy="#snippet">Copy</button>
bk.theme.setStyle('brutal');
bk.theme.toggle(); // light ⇄ dark
bk.theme.random();
bk.toast.success('Saved');
await bk.confirm('Delete this?');
bk.modal('#dialog', 'open');
bk.hotkey('mod+k', openPalette);
bk.copy('text');
bk.announce('Row deleted');
const s = bk.store({ count: 0 });
s.subscribe(state => render(state));
Not a checklist item
Native first
Checkboxes are checkboxes, dialogs are dialogs, details are details. Semantics come free.
Focus you can see
Every skin defines its own ring, and every skin keeps it visible for keyboard users.
Motion respected
prefers-reduced-motion disables animation in CSS and in the runtime.
RTL by construction
Logical properties throughout — no separate right-to-left stylesheet.