Utility CSS has a readability problem. Class strings like p-4 m-8 gap-2 text-xl only mean something after you have memorized the scale. Ask a room of developers what p-4 computes to and you will get confident, contradictory answers. The numbers themselves are not the problem. The problem is that numbers force every reader, human or machine, to keep a translation table in their head.
SenangStart CSS removes that table. The core idea is simple: write your styles the way you would describe them out loud.
<div
layout="flex col center"
space="p:big"
visual="bg:primary text:white rounded:big"
>
Hello SenangStart!
</div>
No classes, and no build step to get started. The styles live in attributes that read like instructions you would give a colleague.
A note on the name: "senang" means "easy" in Malay, and the project ships bilingual documentation in English and Bahasa Melayu. It is a useful reminder that developer ergonomics are also a language problem.
Three attributes instead of one class soup
Most utility frameworks pour every concern into a single class attribute and let you sort it out. SenangStart splits styling into three semantic channels:
| Attribute | Answers the question | Example |
|---|---|---|
layout |
How is it arranged? | layout="flex col center" |
space |
How big, how far apart? | space="p:medium g:small" |
visual |
How does it look? | visual="bg:white rounded:big shadow:medium" |
This sounds cosmetic until you read a component written this way:
<div
layout="flex col"
space="w:[320px] p:medium g:small"
visual="bg:white rounded:big shadow:medium"
>
<h2 visual="font:bold text-size:big text:dark">Card Title</h2>
<p visual="text:grey">Card description goes here.</p>
<button
space="p-x:big p-y:small"
visual="bg:primary text:white rounded:medium"
>
Action
</button>
</div>
You did not parse that markup, you read it. Vertical card, white background, rounded corners, medium shadow, bold title, roomy button. Structure is encoded in the attribute names, so a component stays scannable months later without checking its git history. The markup documents itself.
A scale made of words
The spacing system is where the framework is most opinionated. Instead of p-4 or p-16, you pick from adjectives mapped to physical objects:
| Keyword | Value | Mental model |
|---|---|---|
tiny |
4px | a pebble |
small |
8px | a matchbox |
medium |
16px | a smartphone |
big |
32px | a laptop |
giant |
64px | a door |
vast |
128px | a house |
The important property here is that the ordering is already baked into the language. Nobody memorizes that big is larger than medium; you already know. When a section needs more breathing room, you do not compute a pixel value. You move one step up the ladder: medium becomes big.
The docs call this the intent-first mindset. Stop thinking in pixels and describe what you mean. When you need an exact value, square brackets accept any literal:
<div space="w:[350px] p:[20px_40px]">Exact values when you need them</div>
Why this matters for AI tooling
Utility frameworks were designed for humans who memorize abbreviations, but a growing share of frontend code is now written by language models. LLMs handle natural language well and arbitrary numeric scales poorly.
Ask an assistant to "make the padding larger" in a Tailwind codebase and it has to guess: p-5, p-6, or p-8? In SenangStart the same request has one correct answer on an ordered scale:
"Make the padding larger"
AI: changedp:mediumtop:big
The vocabulary carries the semantics. Requests like "tighten it up" or "give it breathing room" translate directly into moves along the adjective scale. The project also ships an llms.txt file so coding assistants can ingest the full grammar. A human readability feature turns out to be a machine readability feature too.
Two runtimes: zero build or real build
SenangStart ships two paths, depending on how much tooling you want.
The zero-build path is a single script tag. A browser-side JIT engine scans the DOM for layout, space, and visual attributes, compiles the CSS on the fly, injects a style tag, and keeps watching with a MutationObserver so dynamically added elements get styled as well. This fits prototypes, demos, and learning the framework.
<script src="https://unpkg.com/@bookklik/senangstart-css/dist/senangstart-css.min.js"></script>
The production path is a small CLI, available as both senangstart and the shorter sen:
npm i @bookklik/senangstart-css
sen init # creates senangstart.config.js
sen dev # watch and rebuild
sen build --minify
One detail worth calling out: the build fails on invalid tokens by default. Typos produce a non-zero exit code at compile time instead of silently missing styles at runtime. You can opt into warnings with --ignore-invalid, but the strict behavior is the default, which is the safer way around.
Migrating from Tailwind
Migration is usually the hardest part of adopting a new styling system, so SenangStart ships a Tailwind conversion engine. Point it at your HTML and this:
<div class="flex items-center p-8 bg-blue-500 text-white rounded-lg">
becomes this:
<div layout="flex items:center" space="p:big" visual="bg:blue-500 text:white rounded:medium">
The converter runs as a Node script from the repository, or as a browser bundle exposed at window.SenangStartTW.convertHTML(html). If you are uncomfortable with the semantic rounding, --exact mode preserves Tailwind's numeric values behind a tw- prefix, so p-4 becomes space="p:tw-4". That lets you migrate mechanically first and move to the semantic scale gradually.
Other details worth knowing
- Responsive design uses breakpoint prefixes:
layout="flex mob:block tab:flex-row lap:gap:medium"acrossmob,tab,lap, anddeskbreakpoints. - Interactive states live inside
visual:hover:bg:light focus:ring:thin active:scale:95. - Dark mode is a prefix, not a plugin:
bg:white dark:bg:dark, driven by media query or selector, configured in one line. - Peer interactions get dedicated attributes. Mark a trigger with
interact="card"and listeners withlistens="card", and hovering one element restyles the other. - All utilities are generated from a single source of truth in
src/definitions/. The same definitions drive the JIT engine, the CLI compiler, the generated docs, and the TypeScript types, so documentation cannot drift from the compiler.
The trade-offs
No framework is free. Attribute-based styling makes your HTML more verbose, and your editor needs to understand the custom syntax, although generated type definitions help. Teams fluent in Tailwind's numeric scale will feel some friction while their intuition remaps, even with the converter doing the heavy lifting. The CDN JIT is a runtime dependency, so production users should compile ahead of time. And adjectives are a coarser vocabulary than numbers, so very fine-grained design tokens eventually push you toward bracket syntax or a custom theme.
These costs are real. Whether they are worth it depends on what you value. If your UI is maintained by both humans and machines, if design reviews keep stalling on what gap-3 means, or if you want markup that a non-engineer can partially read, the trade tilts toward the words.
Try it in sixty seconds
Save this as index.html and open it in a browser.
<!DOCTYPE html>
<html>
<head>
<title>Sixty Seconds</title>
<script src="https://unpkg.com/@bookklik/senangstart-css/dist/senangstart-css.min.js"></script>
</head>
<body>
<div
layout="flex col center"
space="p:big g:small"
visual="bg:primary text:white rounded:big"
>
<h1 visual="text-size:giant font:bold">Hello SenangStart!</h1>
<p>Zero config. Reads like English. Works like a utility framework.</p>
</div>
</body>
</html>
Utility CSS won on being mechanical. SenangStart is betting the next step is being readable, both to the humans maintaining the markup and to the models increasingly writing it. The numbers were never sacred. They were just the best available option before someone wrote CSS the way people actually talk.
Links
- Docs: bookklik-technologies.github.io/senangstart-css
- GitHub: bookklik-technologies/senangstart-css
- npm:
npm i @bookklik/senangstart-css, MIT licensed