Florian Schroedl 8a6559491b fix(calendar): grey week grid lines in dark mode + align columns
Use the semantic --border-0 token for week grid borders so they adapt to
dark mode (muted grey) instead of a hard-coded near-white --gray-200.
Move the header + all-day rows inside the .cal-week-body scroll container
as a sticky .cal-week-topbar so every row shares the scrollbar-reduced
width, eliminating the header/body column drift.
2026-09-10 12:10:22 +02:00
2026-03-03 11:02:08 +01:00
2026-03-03 11:02:08 +01:00

clj-ui-framework

A cross-target component library for Clojure (Hiccup), ClojureScript (Replicant), and Squint (Eucalypt). Components are .cljc files that compile to all three targets using reader conditionals.

Live Demo

The full component gallery is deployed at ui.hetzner.florianschroedl.com (Hiccup target, behind Tailscale). It is push-to-deploy via the clj-ui-framework project on the Hetzner host — pushing to the bare repo runs bb build-theme and restarts the server.

Installation

Add as a git dependency in bb.edn or deps.edn. Do not use git submodules or worktrees — they cause stale checkouts, missing theme CSS, and broken builds.

Clojure / Babashka

;; bb.edn or deps.edn
{:deps {clj-ui-framework/clj-ui-framework
        {:git/url "https://gitea.florianschroedl.com/floscr/clj-ui-framework"
         :sha "<latest-sha>"}}
 :paths ["src"]}

Babashka / Clojure resolves the dependency automatically — no manual path setup needed.

(ns my-app.core
  (:require [ui.button :as button]
            [ui.card :as card]
            [ui.theme :as theme]))

(button/button {:variant :primary} "Click me")

ClojureScript (Replicant / shadow-cljs)

Add as a git dep in deps.edn (shadow-cljs reads it):

{:deps {com.gitea.florianschroedl/clj-ui-framework
        {:git/url "https://gitea.florianschroedl.com/floscr/clj-ui-framework.git"
         :git/sha "<latest-sha>"}}}

Squint

Squint doesn't resolve git deps — it only reads :paths from squint.edn. Use the built-in ui.setup helper to symlink sources and copy theme CSS:

;; bb.edn — the git dep is declared here
{:deps {clj-ui-framework/clj-ui-framework
        {:git/url "https://gitea.florianschroedl.com/floscr/clj-ui-framework"
         :sha "<latest-sha>"}}
 :paths ["src"]
 :tasks
 {frontend:setup
  {:doc "Setup clj-ui-framework for squint frontend"
   :requires ([ui.setup :as setup])
   :task (setup/setup! {:link-target "app/lib/ui"
                        :theme-target "app/lib/theme.css"})}

  frontend {:depends [frontend:setup]
            :task (shell {:dir "app"} "npm run watch")}}}

Then in squint.edn:

{:paths ["src" "lib/ui"]}

Add lib/ui and lib/theme.css to .gitignore — they're generated by the setup task.

CSS (theme.css)

The generated dist/theme.css contains all design tokens (colors, spacing, typography, borders, shadows) and component styles. You must include it — component CSS references variables defined in the theme.

  • For Babashka/Hiccup apps: inline theme CSS or serve it from the git dep's dist dir
  • For browser apps: the setup task copies theme.css to your app dir
<link rel="stylesheet" href="lib/theme.css" />

For dark-only apps, set data-theme="dark" on the <html> element. Otherwise dark mode activates automatically via prefers-color-scheme.

Do not manually duplicate theme tokens in your app's CSS. If component styles look broken (missing colors, wrong spacing), you're missing theme.css.

Updating

Bump the :sha in bb.edn / deps.edn and re-run the setup task:

bb frontend:setup   # re-symlinks and copies fresh theme.css

Development

Requires Babashka.

bb build-theme   # Generate dist/theme.css
bb test          # Run tests

For dev servers (Hiccup on :3003, Replicant on :3001, Squint on :3002):

bb dev-all       # Starts all three in a tmux session

Replicant and Squint need npm install in their dev directories first.

Components

Accordion, Alert, Badge, Breadcrumb, Button, Card, Dialog, Form, Icon, Pagination, Progress, Sidebar, Skeleton, Spinner, Switch, Table, Tooltip.

Each component is a .cljc file in src/ui/ with a matching .css file. The CSS is collected automatically during theme generation.

Color System

Colors are generated from OKLCH parameters defined in src/theme/tokens.edn. OKLCH is a perceptually uniform color space — equal lightness steps produce equal perceived brightness across all hues, unlike HSL. You define a hue and chroma, and the generator produces an 11-stop scale (50950) for each color, output as oklch() CSS values.

Five scales ship by default: gray, accent, danger, success, warning.

How it works

Each scale is defined by a hue, a default chroma, and a list of lightness steps:

:gray {:hue 285 :chroma 0.025
       :steps [[50  0.975 0.003]   ;; [label lightness chroma]
               [100 0.955 0.005]
               [200 0.915 0.010]
               ...
               [950 0.145 0.011]]}

This generates CSS variables in :root:

--gray-50:  oklch(0.975 0.003 285);
--gray-100: oklch(0.955 0.005 285);
--gray-200: oklch(0.915 0.010 285);
...
--gray-950: oklch(0.145 0.011 285);

Semantic tokens reference these scales. Light theme points at the light end, dark theme at the dark end:

;; Light
:bg-0 "var(--gray-50)"
:fg-0 "var(--gray-950)"

;; Dark
:bg-0 "var(--gray-950)"
:fg-0 "var(--gray-50)"

The scale variables are generated once and never change between themes. Only the semantic mapping shifts.

Changing the palette

The gray hue controls the tint of all neutral surfaces, borders, and text. OKLCH hues differ from HSL — change it to shift the entire feel:

Hue Result
285 Purplish gray (default)
255 Blue-gray
60 Warm/sandy
any, chroma 0 Pure neutral

The accent hue controls buttons, focus rings, links:

Hue Result
286 Purple (default)
255 Blue
25 Red
165 Green

After changing values, run bb build-theme to regenerate dist/theme.css.

Per-step chroma

Steps can be [label lightness] to use the scale's default chroma, or [label lightness chroma] to override it. This is important for chromatic colors where high chroma looks wrong at the extremes — the accent scale peaks at chroma 0.255 in the mid-range and tapers to 0.020 for light tints and 0.130 for dark shades. The gray scale uses very low chroma (0.0030.035) that peaks in the mid-range for a subtle tint.

Current palette

The default ships with a purplish gray (hue 285) and a vivid purple accent (hue 286, --accent-500: oklch(0.595 0.23 286)), inspired by the activity-tracker app. OKLCH ensures the lightness steps are perceptually even — a gray at L=0.5 looks equally bright regardless of hue, unlike HSL where different hues have wildly different perceived brightness.

Theme Tokens

Beyond color scales, the theme includes:

  • Size scale--size-1 (0.25rem) through --size-16 (4rem), linear
  • Font scale--font-xs through --font-3xl, geometric (ratio 1.25)
  • Borders--border-0/1/2, referencing gray scale stops
  • Shadows--shadow-0/1/2/3, increasing elevation
  • Radii--radius-sm (6px), --radius-md (10px), --radius-lg (16px)

Light/dark mode switches automatically via prefers-color-scheme, or manually with data-theme="dark" on the root element.

S
Description
No description provided
Readme 3.5 MiB
Languages
Clojure 69.5%
JavaScript 14.6%
CSS 12.6%
HTML 3.3%