struts: a CSS and JS starter for web projects
A starting point you copy and tailor, not a library you install. Opinionated, and shaped by the sites we build.
Principles
- Copy it, don't install it
- Each project takes the parts it needs and changes them freely. There's no version to keep up with.
- The platform first
-
<details>,<dialog>,commandfor,popoverandcolor-mix()do most of the work. JS fills the gaps, and everything works without it. - Tailwind underneath, used sparingly
- Tokens and utilities come from Tailwind v4. Patterns are BEM blocks with a small custom-property API, so markup stays readable.
- Colour by context
- Set a surface and everything inside follows it. No per-component overrides.
- Written down
- Each file's header comment is its API, and these pages show it. Each choice has a short decision doc explaining why.
Layers
struts is built up in layers, each with its own page. A layer can hold CSS, scripts or both: what decides the layer is what a thing is for, not what it's written in.
-
css/tokens/ · css/base/ · colors.config.json
Tokens, shared knobs and element defaults: what every page gets before a single class. Colour, type, spacing and bare HTML.
-
css/patterns/ · js/
Reusable blocks that work on any markup, each with a custom-property API. Some have a script, hung off
data-*hooks: disclosure, dialog and animate on scroll. -
css/utilities/ · js/
Single-purpose helpers: classes like
stack-16andrule-block-start, and functions likedefine()anddebounce(). Tailwind's own utilities sit alongside them. -
components/
Markup, styles and behaviour that only work together, one folder each, built from everything above.
css/index.css imports tokens, base, patterns, components and utilities, in
that order. Element defaults sit in Tailwind's base layer, so any class beats them. Why:
CSS layers and file order.
One more folder, css/adapters/, maps a platform's classes onto patterns
(WordPress, for now). A project imports one if it needs it.
Naming
Ask one question of anything new: is it owned by one pattern, or read by several? The answer decides its name.
- Global knobs
-
Single dashes, in Tailwind's shape:
--color-accent,--gutter,--space-layout. - Pattern variables
-
--{block}[__{element}]--{property}[--{state}], with CSS property names:--btn--background-color--hover. Modifiers never own variables. - Classes
-
Patterns and components are BEM (
btn btn--secondary). Utilities are Tailwind-shaped (stack-16). Colour contexts aresurface-{colour}andon-{colour}. - JS hooks
-
Behaviour hangs off
data-*attributes, never classes:data-disclosure,data-dialog.
The full rules: docs/naming.md.
Decisions
Why things are the way they are, one topic each.
Get started
Give your coding agent this prompt from your project's root:
Adopt struts (https://github.com/kite-co-code/struts) into this project. Clone it to a temporary folder and follow its ADOPT.md. Plan the work in phases that each leave a working build, starting with the foundation.
ADOPT.md is the full procedure, for agents and people alike.