Skip to content
Bukalemun
Install Skins Components JavaScript
v1.3 · zero dependencies

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.

Get started

Currently wearing Default

Skins
Components
Dependencies
CSS, gzipped
Install

Two lines and you are done

No build step, no config file, no plugin ecosystem to learn.

CDN — what you should ship · 32 KB gzipped
<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>
CDN — all fifty in one file · 92 KB gzipped
<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
npm install bukalemun

// then
import 'bukalemun/css/base';
import 'bukalemun/styles/brutal.min.css';
import bk from 'bukalemun';
Why two stylesheets?
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">
Skins

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.

Tokens

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.

Overriding a token
/* 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; }
Components

Sixty-plus pieces, all skin-aware

Switch skins while you scroll — every example below follows along.

Buttons

Appearance
Colour
Size & shape
State
Groups

Forms

Tags
Choice controls
Choice cards Range & rating
062100
One-time code
File drop
Drop files here or click to browse

Feedback

Heads up
This alert borrows its icon, colour and radius from the active skin.
Deployed
Build 4127 is live.
Quota at 82%
Payment failed
We could not charge the card on file.
Progress
Uploading68%
72%
Toasts

Programmatic, queued, pausable, promise-aware.

Skeletons

Data display

4 rows
Project Owner Builds Status
bukalemun ŞH Şahan 1284 Passing
temrensec AK Ada 312 Flaky
maisonask MK Mert 97 Failing
octogent ZL Zeynep 2041 Passing
Newv1.0

Card with media

Hover it. The lift, the shadow and the border all come from the skin.

Timeline
  1. 09:12 Commit pushed feat: add 50th skin
  2. 09:14 Build running Compiling tokens…
  3. Deploy Waiting
How does a skin change everything at once?
Components only ever read semantic custom properties. A skin redefines those properties under its own attribute selector, so a single attribute swap repaints the entire tree without touching a single component rule.
Can I ship only one skin?
Yes — load bukalemun.base.min.css plus one file from dist/styles/.
Does it need JavaScript?
No. Every visual component is pure CSS. The runtime only adds behaviour — overlays, sorting, toasts, skin persistence — and degrades to plain HTML without it.
zero dependencies 50 skins dark mode RTL ready a11y first 50 KB gzipped

Overlays

Built on native <dialog>: real top layer, real focus trapping, real Esc.

CSS tooltip
Layout

Intrinsic, not breakpoint soup

Primitives that reflow on their own: stack, cluster, switcher, grid-auto, reel, sidebar.

grid-auto
wraps
without
breakpoints
Make your own

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.

my-skin.css
JavaScript

A runtime, not a framework

Everything is opt-in through data-bk-* attributes, and everything has a programmatic twin.

Markup-driven
<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>
Programmatic
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));
Accessibility

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.

Native dialog

Focus is trapped, the page is inert, Esc closes it.

Drawer

Slide it in from any side with data-side.

Esc
Anchored popover

Flips when it would overflow the viewport, shifts to stay in view, and points its arrow at the anchor.