Boss-CSS is a polymorphic "CSS-in-JS" library supporting multiple different ways of applying CSS to your codebase, with or without runtime.
Before I get into the details there are a few things I want to clarify. I'm not a content creator or writer, and I don't want to be. For this reason, this will be one single, detailed, long ass post. I'll try to explain and justify the choices I made inspired by my past experiences. It'll also serve as some sort of initial documentation.
In case you want to read about the library itself and skip its history, jump here: Boss CSS
History
I'm coding mostly for web for 22 years now, and for 17 professionally. I've used several different ways to do CSS, using all kinds of frameworks, methodologies, different styling solutions, compilers and architectures. We always change for the better, trying to overcome the problems our previous solution suffered from. But this doesn't mean the new solution performs better in all areas, always leaving a gap behind.
My first stop was using raw CSS. To solve organization issues, I started to split files into multiple chunks. To simplify selector usage, I've started to use LESS and SASS with support for nesting. To solve issues with naming and avoid selector collision, I was using methodologies like Atomic CSS and BEM. To simplify usage in the codebase and enable tree-shaking, I started to use CSS Modules. To improve DX, I started to use CSS-in-JS based solutions. To solve runtime performance issues, I started to use build-time static extraction. I started to use Tailwind, because it's lightweight and framework agnostic. All the solutions are coming with their own strengths and flaws, which would deserve their own articles.
Around 8 years ago, I chose Grommet as the UI layer for a project. I immediately fell in love with their Box component, but I still ended up writing custom style objects for specific needs. A few years later, on another project, I ended up creating my own Box component. At the end it got quite complex and still, it didn't give me full CSS support, which again required me to write custom style objects and custom class names in CSS files.
This was the time when CCSS and YouEye were born. It fundamentally changed how I wrote and used CSS. I'm still using them in production today, but (as it should) they started to reveal their own weaknesses over time.
My relationship with Tailwind
The last couple of years it became unavoidable to work with Tailwind on a project. I'm using it both in personal and professional projects. It's a classic love-hate relationship. I always loved the idea of utility classes and Atomic CSS. In fact, I've created my first utility class based CSS framework on top of SASS 11 years ago. It was generating utility classes based on configuration, then it was purging the final CSS based on usage. The final results were actually pretty close to Tailwind's AOT compiler.
However, I have some problems using Tailwind:
- I always end up in situations where I have to keep maintaining a separate styling solution for custom needs. Tailwind is only a subset of CSS and it won't cover every area.
- The naming of selectors: I wouldn't be able to count on my 2 hands how many times I've Googled the term
tailwind line-height, because of course, it'sleadingand I keep forgetting that. What'sfont-sizein CSS, it'stext-[size]in Tailwind. These minor differences are driving me crazy, and in some cases it's seriously interrupting my flow. - Dynamic cases: whether Tailwind or CSS Modules, I always disliked merging class names using 3rd-party utilities, and by hand using conditions.
- States and pseudo elements: I hate using
hover5 times just to style my hover state. - Breakpoints: I always hated mobile-first approaches, especially nowadays when it doesn't really make sense anymore.
Please note that at the time of writing this, we only had Tailwind v2 available. It came a long way since then, solving many problems, especially around custom CSS.
I understand those who are in favor of Tailwind over native CSS. It actually solves a lot of quirks and issues coming with native CSS. I believe it's a much better choice for those who started web development in the last couple of years or have a more backend-heavy background and feel uncomfortable writing CSS directly.
However, I spent a lot of years learning, understanding and overcoming these problems. In most cases, I simply want to write CSS, and Tailwind just doesn't give me that.
Today
I actually never intended to write another CSS-in-JS library. But over the years I had so many thoughts about what I'd do differently, how I'd solve certain problems. Sometimes I had sleepless nights thinking about styling in different ways. Over time, these thoughts piled up and I've started to connect the dots in my mind. Last year, during such a night, I've decided to get up and test a few things in an online playground. It was the moment when I decided to start this new project, because I felt like I could offer something new.
The biggest question for me: which solution made me the happiest, the most productive, and had the best possible balance in terms of both DX and UX? For me it was prop-based CSS-in-JS with static and dynamic extraction.
Since working remotely I've joined several short-term projects either as a developer or consultant. I've seen dozens of cases where projects had their own Box component, each of them suffering from different flaws. This was also a fueling factor, because I know it could be better, I did use better in the past.
Boss was born
When I first started to work on this new library, my goal was to create a solution that's tooling independent. YouEye and many existing tools are mostly relying on custom Babel plugins by parsing and modifying AST at build time. I moved away from Babel years ago due to performance reasons, first to ESBuild then to SWC. I wanted a way that works regardless of the stack a project is using. Tailwind introduced such a way: parse on your own.
Initially, I simply wanted to have prop-based CSS-in-JS that's extracted as Atomic CSS, while having a lightweight runtime that doesn't emit or manipulate stylesheets. I also wanted to make sure that it's fairly easy to extend its functionality, which led me to an event-based solution for its overall architecture. Soon I realized that this deep control enables me to add support for different syntaxes and extraction strategies, so users can decide which combination suits their project best. This is the reason for the name Boss, it lets you be in control.
I spent a lot of my free time working on the library. Unfortunately, I don't really have much time next to my family and work, which led to very slow progress. At the beginning of 2024 I also had some personal issues, which forced me to pause every personal project. This slow progress definitely caused some fragmentation in my code. However, I spent so much time working on this and I truly believe it offers something new, I simply don't want it to go to waste.
For the above reasons I decided to bring the project into shape for an early Alpha release. I did, it was 90% ready. However after 3 months of sleepless nights and grinding next to work and the fam, I hit a rough burnout phase. Only some minor bugfixes, documentation, website, playground and such were left. I spent so much time away from the project that I ended giving it up.
Last year I slowly moved over to vibe-coding. Today, I mostly just review and instruct the agent's work. I decided to "revive" the project in the middle of boredom. This time with AI only, taking care of all the missing parts. Well, from this point I quickly got into the rabbit hole, asking the AI to add features I previously planned to implement in a future version, features I didn't even want to implement. It only took ~4 weeks of some free time to finish all this. Not the best quality of results, but it works enough.
My hope is that it'll gain some traction in the community, which would force me to find a way to work on it more seriously. I hope to get some help to shape its rough edges, and help to build reliable documentation. In case those things don't happen, I still wanted to put it out there and hopefully inspire fellow developers with some ideas my solution brings to the table.
Honestly, I'm not even sure if the lib has relevance nowadays in this AI era.
Boss CSS
When building Boss, I tried to bring the best parts of different styling solutions I used over the years. While that is subjective, I believe I achieved that for the most part.
It's just CSS
I often see libraries and frameworks coming with custom props, custom namings for some cases, like the p and m prop only for margin and padding, but nothing else, or consolidating font-* and text-* properties under a single prefix. These add extra learning curves, and can disrupt the flow of those coming with a heavy native CSS background.
Parity with CSS naming and syntax was one of the most important goals for me. I wanted to be able to write CSS as it is as much as possible, without having to translate it in my head to some custom syntax. Class name based styling is basically almost inline styles by look.
If you know CSS, you know 98% of Boss.
Multiple syntax support
I'm going to showcase all the different syntaxes supported out of the box.
Why support multiple syntax? Because it lets you mix-n-match according to your own preferences, and your own specific needs/use cases.
Prop based styling
<$$ color="red">
Prop-based styling is the default for me for a long time now. I simply love it more than anything else. It comes with great flexibility, handles dynamic cases very well, is highly customizable for different design system needs, and can be perfectly typed.
Read more in the Docs: JSX usage.
Class name based styling
<div className="color:red">
Using class names is really convenient, but when it comes to dynamic cases, this is where it can get messy. In Boss, using class names really just looks like using inline styles. It has almost identical syntax, with the additional benefit of targeting states, pseudo elements, media queries, keyframes, tokens, and more.
Read more in the Docs: Classname usage.
Object based styling
<span {...$$.style({ color: 'red' })}>
It's basically prop-based styling with a different syntax, but if you prefer objects instead of props, no problem. (I know in this example it doesn't look any different from an ordinary style object, and that's a good thing.)
Prepared components
$$.MyStyledContainer = $$.$({
width: 1200,
})
const Component = () => <$$.MyStyledContainer>...</$$.MyStyledContainer>
A concept Styled Components popularized in the first place, and it's really handy to create reusable elements.
Read more in the Docs: Prepared components.
Raw CSS
If needed, you can still append raw CSS blocks with $$.css template string/object forms. It remains useful for custom selectors and one-off rules. Because sometimes we still need it.
$$.css(`
#element .selector ul li {
/* my custom css */
}
`)
Because sometimes we need it.
Mixed
<$$.a
className="display:flex place-content:center"
color={linkColor}
hover={{ background: 'red', borderBottom: '1px solid #000' }}
>
Click me!
</$$.a>
Bosswind
An optional plugin that provides Tailwind-like shorthands and tokens out-of-the-box.
// As classNames
<$$ className="flex px:1 text:xl" />
// As props
<$$ flex px={1} text="xl" />
In this example I used class names for static styles, and props for dynamic cases, as well as the hover prop instead of multiple hover:* classes.
Read more in the Docs: Bosswind.
Runtime?!
As you might have seen in the previous examples, prop-based styling is done through a custom component, which might make you think runtime is required. And you're not wrong. However, you have the option to compile away all the components to native elements in a bundler-agnostic way, or just use class names, which doesn't need runtime.
Why I call Boss a polymorphic CSS-in-JS library is because Boss includes runtime based on your usage. In case you're only using class names with classname-only, runtime files won't be generated at all. Plugins can determine when and what code to include based on how you're using styles. This makes it possible to use Boss class names in basically any stack/environment, regardless of framework or programming language.
Having a lightweight runtime can be just as good as not having any, but the most important part is that it can give superpowers. There are many never-seen features in my head utilizing runtime, and I'm eager to share them over time. One I can share is the experimental devtool: when you select a DOM element on the page, you can adjust its styling or source code right in the browser, hit save, and it writes the changes back to the source file, without leaving the browser.
This polymorphism also applies to TypeScript types and AI files. Boss generates TS types, AI docs, and AI skills based on your configuration, updating them based on your usage. It supports types for custom properties, prepared components and design tokens as well.
Boss runtime also works differently compared to classic CSS-in-JS solutions. In inline-first and classname-first it does not generate full stylesheets on every render. In runtime-only mode it can inject only the needed rules on the client. You can also go hybrid, and have both pre-generated CSS at build time and runtime support for highly dynamic cases, for example when users modify styles or certain rules come from a 3rd-party source. This is one of the biggest ideas behind the architecture.
Read more in the Docs: Runtime strategy.
Extraction strategies
Boss supports different extraction strategies. Again, this is to facilitate different needs.
Inline first
This is the default strategy. Boss prioritizes inline styles, putting everything possible inline, saving a large chunk of external, initial and unused CSS.
Performance is arguable. You can find studies saying inline styles are faster and just as many saying it's slower. In my opinion it's circumstantial. But even if it tends to be slower in some cases, you save time on loading external stylesheets, which are normally blocking requests delaying FCP.
Input:
<$$ color="red" hover={{ color: 'yellow' }} />
Output:
<div class="hover:color" style="color: red; --hover-color: yellow" />
In the above case we only have external CSS for the hover state.
Class name first
This is the opposite of inline first. It uses class names wherever possible and keeps inline only where unavoidable (for example user-flagged dynamic cases using CSS variables). This approach is close to Tailwind-like output.
Input:
<$$ color="red" hover={{ color: 'yellow' }} />
Output:
<div class="color:red hover:color:yellow" />
Fully runtime
There are projects which need full runtime support and need to generate styles on the fly. Projects with complex WYSIWYG editors or page styles coming from separate databases. Full runtime support is for these cases.
Boss supports this with the runtime strategy (runtime.only: true) and also supports hybrid mode (runtime.only: false) when you want server CSS output plus runtime dynamics.
Zero runtime extraction
It is possible to transform source to native HTML elements, classes and style objects at build time, leaving runtime out completely.
Today this can be achieved through:
-
classname-onlystrategy for static class usage -
npx boss-css compilefor source transformation and runtime pruning in supported cases
npx boss-css compile
Simple compile example:
// input
const Example = () => <$$ color="red" padding={8} />
// output
const Example = () => <div style={{ color: 'red', padding: '8px' }} />
Read more in the Docs: Compile, Classname-only strategy.
Framework-agnostic approach
What I dislike about many extraction tools is that they are mostly tied to a custom compiler integration (Babel plugin, framework plugin, bundler lock-in). Boss is intentionally different here.
Boss is built to run through PostCSS, CLI build/watch, or compile mode, without requiring Babel/Webpack/Vite-specific plugins. That makes it easier to carry the same styling API across React, React Native, Next, Preact, Solid, Qwik, Stencil, and non-JSX/non-JS setups using static class names.
Read more in the Docs: Tooling agnostic.
Goodies
It's time to write about other random features and goodies Boss supports.
$$ is global
By default, you can access Boss components and utilities using the $$ global variable. Whoever is old enough to have worked with jQuery can't argue that accessing $ everywhere was pure simplicity, and with inline styles the feeling is no different. Besides, you don't want to import Boss in every single file where you write markup, just as much as you would not want to do import { div, a, span, section } from 'react'.
Powerful plugin system
It's so powerful that basically every feature of Boss is just a plugin. The core is mainly the event system. Plugins are split into server/browser code: server controls compile-time tasks, browser code ends up in your production bundle when needed.
Example for a simple custom prop plugin:
// .bo$$/plugins/bleed/server.js
export const name = 'bleed-prop'
export const onBoot = api => {
api.dictionary.set('bleed', {
property: 'margin',
aliases: ['bleed'],
description: 'Custom bleed prop',
isCSSProp: true,
})
}
export const onProp = async (api, { name, prop, contexts }) => {
if (name !== 'bleed') return
api.css.selector({
className: api.contextToClassName(name, prop.value, contexts),
})
api.css.rule('margin', prop.value)
api.css.write()
}
// .bo$$/config.js
import * as bleedProp from './plugins/bleed/server'
export default {
plugins: [bleedProp /* ... */],
}
<$$ bleed={-14} />
Numeric values use Boss's built-in unit conversion (api.unit), so there is no need for manual ${v}px.
Read more in the Docs: Plugin hooks.
CLI tools
Boss also ships CLI tools for the full workflow:
npx boss-css init
npx boss-css build
npx boss-css watch
npx boss-css compile
npx boss-css dev
So whether you're on PostCSS or a custom/non-PostCSS setup, you still have a clear path to bootstrap, build, watch, and optimize.
Read more in the Docs: CLI.
AI ready
Boss has built-in AI readiness through the AI plugin. It can generate LLMS.md plus an AI skills bundle from your live configuration and usage metadata (tokens, breakpoints, prepared components, strategy context, and more).
This means agents can work with project-specific styling context, not just generic guesses.
Read more in the Docs: AI plugin.
Fontsource plugin
Boss can load fonts from the Fontsource catalog through the fontsource plugin, and injects the needed CSS when fonts is configured.
import * as fontsource from 'boss-css/fontsource/server'
export default {
plugins: [fontsource, /* ... */],
fonts: [{ name: 'Inter', token: "body" }],
}
<$$ fontFamily="body" />
Read more in the Docs: Fonts and Fontsource.
Grouped selectors
You can use multiple styles inside one state/pseudo while using class names. I have to admit that generated selectors for these are not always pretty. But it works, it's there, and it's nice to have. Besides, if you use compiler, that will split up such selectors for you.
<div class="hover:{color:red;text-decoration:underline} mobile:{width:100vh;background:blue}"></div>
At (@) prop and breakpoints
You can write responsive styles with at and with shorthand breakpoints.
<$$ at={{ 'mobile+': { fontSize: 18 } }} />
<$$ tablet={{ fontSize: 18 }} />
ClassName equivalent:
<div class="at:mobile+:display:block" />
<div class="mobile:hover:display:block" />
Built-in defaults include micro, mobile, tablet, small, medium, large, and device, with name, name+, and name- forms.
It also supports arbitrary values and ranges, such as mobile-800.
Boss also supports container queries through container, containerType, containerName, and container_* shorthands.
<$$ containerType="inline-size" containerName="card">
<$$.div container_card={{ mobile: { fontSize: 18 } }}>Child reacts to the parent container</$$.div>
</$$>
ClassName equivalent:
<div class="container-type:inline-size container-name:card">
<div class="container_card:mobile:font-size:18">Child reacts to the parent container</div>
</div>
Read more in the Docs: Pseudo and @ / responsive contexts.
Design tokens
You can access tokens in different ways:
<$$ color="primary"><$$ color={$$.token.color.primary}><div className="color:primary"><div className="color:$$.token.color.primary">
TypeScript hints include available token values for each property.
Boss also supports DTCG/Style Dictionary token JSON and resolves references/inheritance, which makes existing design token pipelines easier to plug in.
On top of this, Boss supports local token theming with the tokens prop, so you can scope theme overrides to a subtree. This is also best shown as a parent override + child inheritance case:
<$$ tokens={{ color: { brand: '#ea580c' }, spacing: { md: 20 } }}>
<$$ color="brand" padding="md">
Parent with local token override
</$$>
<$$.span color="brand">Child inherits the same scoped token override</$$.span>
</$$>
Token strings also support alpha suffixes, for example:
<$$ color="gray.600/60" />
And for DTCG users, composite token types like border, shadow, transition, gradient, and typography are normalized into CSS-ready values.
Read more in the Docs: Tokens.
Powerful TypeScript types
Boss generates type-safe CSS props from real CSS definitions, token-aware props, and prepared component types into .bo$$/index.d.ts.
You also get:
- Token autocompletion and token value hints
- Prepared component typing/JSDoc metadata
- Native runtime typings (
native.d.ts) when React Native runtime is enabled
Passing styles using spread
Boss supports marker/helper APIs for spread-heavy patterns:
const props = $$.$({ color: 'red', hover: { color: 'purple' } })
<$$ {...props} />
For plain elements, use $$.style(...):
<div {...$$.style({ padding: 12, hover: { color: 'purple' } })} />
Read more in the Docs: Spreads and markers.
Target children
Boss supports arbitrary nested selectors both in JSX (child) and class names ([ ... ] syntax).
<$$ child={{ '.title': { color: 'blue' }, '&>div': { color: 'red' } }} />
<div class="[&_.title]:font-weight:700" />
Read more in the Docs: Classname usage.
Server components ready
Boss works with modern server-first frameworks (including Next.js patterns). For Next.js you wire runtime with instrumentation hooks and import CSS manually when css.autoLoad is disabled (which init does by default for Next).
Read more in the Docs: Next.js integration.
Style boundaries
Boss can split generated CSS by directory using *.boss.css boundaries. This lets you load only what you need while shared rules can hoist to common ancestors or global styles.
src/
app/
app.boss.css
page.tsx
admin/
admin.boss.css
page.tsx
.bo$$/styles.css
// src/app/layout.tsx
import './app.boss.css'
Boundary files stay empty in source and are overwritten by Boss on build/watch/postcss runs.
Read more in the Docs: CSS boundaries.
React Native
I've used YouEye in Expo projects, creating cross-device components and I loved it. You can achieve the same with Boss as well.
Boss can generate native runtime output (.bo$$/native.js, .bo$$/native.d.ts) using the same $$ API while mapping props to React Native styles/components.
// .bo$$/config.js
import * as native from 'boss-css/native/server'
export default {
plugins: [native /* ... */],
}
export default function App() {
return (
<$$ padding={12}>
<$$.Text>Native UI</$$.Text>
</$$>
)
}
Read more in the Docs: React Native usage.
Use it as a companion to Tailwind
Boss class names do not collide with Tailwind's by default, which means you can use them together. You can use Boss for CSS cases Tailwind doesn't support, or to handle dynamic cases more nicely.
If you want a more Tailwind-like flavor inside Boss itself, there is Bosswind for aliases/booleans on top of the same pipeline.
Variants and class composition
Boss includes $$.cx, $$.cv, $$.scv, $$.sv (and direct imports) for class/style composition patterns. This makes conditional/variant flows cleaner than hand-rolled class merging.
The $$ component's className also has built-in support for cx.
<$$ className={{ 'display:none': isHidden }} />
Read more in the Docs: Class composition and variants.
Gotchas
Boss has its own gotchas as well. A few important ones:
- ClassName parsing supports static strings only. Dynamic template interpolation is intentionally skipped.
- In
runtime.only, className parsing is disabled (runtime only handles props and nested contexts). -
classname-onlyhas zero runtime, so no dynamic function values. - Compile mode currently focuses on JSX and the main extraction strategies.
- ClassName autocomplete in TS is naturally limited because strings are strings. ESLint helps here.
ESLint plugin
This one already exists now, and I consider it essential. Boss can be used in many different ways, thus it can be abused just as much. Linting gives control to your projects to keep everything in line.
Read more in the Docs: ESLint plugin.
Closing notes
Why "CSS-in-JS" is in quotes
Boss sits in an in-between space. Depending on strategy, it can look like classic CSS-in-JS, static extraction, utility-class generation, or runtime-only behavior. That's why I keep putting it in quotes.
Generated files
Generated files can look messy. Necessary? In my opinion, yes (kinda). They are the practical output of the polymorphic approach, and they keep source authoring clean while letting the pipeline adapt to usage.
What's next?
Nothing. I'm not intending to work on this project for now. I just wanted to put it out there, and hopefully inspire some people with the ideas behind it. If it gains traction, maybe I'll find a way to work on it more seriously, but for now, it's done. I'm using it myself in a project now, it helps reveal some rough edges and bugs which I might or might not fix.
Links
The playground is WebContainer-powered and runs a full Vite + React project in the browser, with editor, terminal, and preview. It also has ready starter templates and shareable URL state, so you can deep-link a template (https://bosscss.com/playground#template=boss-bosswind) or share your exact workspace setup. If you're curious, this is the fastest place to try ideas, push edge cases, and see how Boss behaves before touching your local setup.