<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[effnd.tech]]></title><description><![CDATA[Simple solutions for typical tasks]]></description><link>https://effnd.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad39b919e81ec63bd518e1a/513bbdb2-ab68-4c47-956a-c981fe384329.png</url><title>effnd.tech</title><link>https://effnd.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 13 Nov 2026 19:13:15 GMT</lastBuildDate><atom:link href="https://effnd.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[EffCSS v6: Self-Confident CSS-in-TS for Reimagined Frontend]]></title><description><![CDATA[Discover EffCSS v6 — a zero-dependency, tree-shakeable CSS-in-TS library that is lazy by default and has built-in SSR support.]]></description><link>https://effnd.hashnode.dev/effcss-v6</link><guid isPermaLink="true">https://effnd.hashnode.dev/effcss-v6</guid><category><![CDATA[CSS]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[frontend]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[effnd-tech]]></category><dc:creator><![CDATA[Marat Sabitov]]></dc:creator><pubDate>Sun, 01 Nov 2026 15:43:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad39b919e81ec63bd518e1a/69843707-e71a-4bb9-8790-9a99071ef7da.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://effnd.tech/css/">Docs</a> | <a href="https://github.com/msabitov/effcss">GitHub</a> | <a href="https://www.npmjs.com/package/effcss">NPM</a></p>
<p>The first release of EffCSS was almost two years ago — back then, it seemed that simply leveraging browser APIs and creating some basic styles would be enough to build a good tool. However, the architectural constraints of the library gradually led it toward a more restrained and balanced API.</p>
<p>EffCSS v5 introduced the concept of "lazy" styles — where CSS is generated only upon first use, but this approach presented several challenges:</p>
<ul>
<li>How should the lazy generation mode be controlled?</li>
<li>How can lazy and synchronous CSS creation modes be reconciled?</li>
<li>Most importantly, how can we ensure that the build tool is still able to remove unused Effcss styles from the bundle?</li>
</ul>
<p>Creating styles via EffCSS v5 utility calls introduced side effects at the module level. A conditional "lazy" mode could mislead an inexperienced user, while the public API had grown excessively large — daunting users with its various naming conventions (e.g., <code>className</code> / <code>classNames</code> / <code>lazyClassName</code> / <code>lazyClassNames</code>).</p>
<p>It is also worth noting that the concept of independent stylesheets proved to be quite cumbersome: to take advantage of TypeScript’s autocompletion, one had to define the entire stylesheet, even if only a few classes were actually being used.</p>
<p>After analyzing all these issues, I realized it was time for a new major release — one that sheds everything acting as dead weight or a constraint. Meet EffCSS v6.</p>
<h2>Tree-shakeable and lazy by default</h2>
<p>The new version is built on a few simple ideas that stay consistent across every utility:</p>
<ul>
<li><strong>Unified token-based.</strong> Style utilities return token resolver functions.</li>
<li><strong>Lazy by default.</strong> Styles are materialized only when a token resolver is first used (<code>resolver()</code> or <code>resolver + ''</code>). If a rule references other tokens, wrap it in a function <code>() =&gt; ({...})</code> so it stays lazy.</li>
<li><strong>Tree-shakeable.</strong> Each utility is a standalone export; importing only what you need keeps the bundle small.</li>
<li><strong>Native CSS nesting.</strong> <code>&amp;</code> refers to the current selector, no extra syntax over CSS.</li>
<li><strong>Tokens as keys / values.</strong> Inside functional forms you can pass all EffCSS tokens (selectors, variables, animations, etc), resolved lazily when the rule is used.</li>
</ul>
<p>The tree-shakeability is easy to verify: the bundle size grows only with the utilities you actually import. Importing a single utility keeps the bundle tiny, and even the whole library stays compact:</p>
<table>
<thead>
<tr>
<th>Import</th>
<th>Size</th>
</tr>
</thead>
<tbody><tr>
<td><code>only variable</code></td>
<td>3398 B (3.32 KB)</td>
</tr>
<tr>
<td><code>only className</code></td>
<td>3606 B (3.52 KB)</td>
</tr>
<tr>
<td><code>only theme</code></td>
<td>3927 B (3.83 KB)</td>
</tr>
<tr>
<td><code>all utils</code></td>
<td>7422 B (7.25 KB)</td>
</tr>
</tbody></table>
<h2>Minimalistic API</h2>
<p>The public API is deliberately small and consistent. Every utility is a standalone export, and they all follow the same "token resolver" pattern:</p>
<table>
<thead>
<tr>
<th>Utility</th>
<th>Description</th>
<th>Returns</th>
</tr>
</thead>
<tbody><tr>
<td><code>id</code> / <code>className</code></td>
<td>Unique <code>#id</code> / <code>.class</code> selector rule</td>
<td>selector resolver</td>
</tr>
<tr>
<td><code>variable</code></td>
<td>CSS custom property (<code>@property</code>)</td>
<td>variable resolver</td>
</tr>
<tr>
<td><code>animation</code></td>
<td><code>@keyframes</code> rule</td>
<td>name resolver</td>
</tr>
<tr>
<td><code>container</code></td>
<td><code>@container</code> rule</td>
<td>container resolver</td>
</tr>
<tr>
<td><code>font</code></td>
<td><code>@font-face</code> rule</td>
<td>font-family resolver</td>
</tr>
<tr>
<td><code>layers</code></td>
<td><code>@layer</code> declaration</td>
<td>object of layer resolvers</td>
</tr>
<tr>
<td><code>style</code></td>
<td>Arbitrary styles</td>
<td>styles resolver</td>
</tr>
<tr>
<td><code>theme</code></td>
<td>Group variables into theme variants</td>
<td>theme controller</td>
</tr>
<tr>
<td><code>serialize</code></td>
<td>Serialize styles into <code>&lt;style&gt;</code> tags</td>
<td>string</td>
</tr>
<tr>
<td><code>clear</code></td>
<td>Remove all styles and reset counters</td>
<td><code>void</code></td>
</tr>
<tr>
<td><code>setConfig</code> / <code>getConfig</code></td>
<td>Set / get the global context config</td>
<td><code>boolean</code> / <code>Settings</code></td>
</tr>
<tr>
<td><code>context</code> / <code>getContexts</code></td>
<td>Create / list isolated style contexts</td>
<td><code>Context</code> / <code>Record&lt;string, Context&gt;</code></td>
</tr>
</tbody></table>
<p>The most important changes in the API:</p>
<ul>
<li>There are no longer any utilities for generating data attributes; while that might have been convenient, it certainly wasn't the best idea from a semantic standpoint — in any case, CSS classes and IDs are more than sufficient for creating selectors.</li>
<li>No more metadata during serialization — essentially, you only need serialization to prepare styles in advance, before client-side rendering; any additional strings in the HTML would simply increase network traffic.</li>
<li>There is no explicit access to the stylesheets — this capability was essentially an "emergency hatch" that allowed for interference with how EffCSS operates.</li>
<li>Explicit management of the state and lifecycle of each style context — so that you can explicitly clean up the resources that have been created.</li>
</ul>
<h2>Tailwind-style modifiers</h2>
<p>Selector resolvers (<code>id</code> and <code>className</code>) accept one or more <strong>modifiers</strong> to create variants of the same base rule. Each variant gets its own token, cached per modifier set. Pseudo-classes and attributes are appended to the selector, while at-rules (<code>@media</code>, <code>@container</code>) wrap the rule at a different nesting level:</p>
<pre><code class="language-ts">const box = className(() =&gt; ({ color: 'red' }));

box(); // 'f_6-0' -&gt; `.f_6-0 { color: red; }`
box(':hover'); // 'f_6-1' -&gt; `.f_6-1:hover { color: red; }`
box('@media (max-width: 700px)'); // 'f_6-2' -&gt; `@media (...) { .f_6-2 { ... } }`
box('@media (max-width: 700px)', ':hover'); // 'f_6-3' -&gt; `@media (...) { .f_6-3:hover { ... } }`
</code></pre>
<p>The same base rule is reused across variants — conditions are applied <strong>where the rule is used</strong>, not baked into its definition, unlike native CSS nesting.</p>
<h2>Complex and arbitrary styles</h2>
<p>Beyond single-rule generators, EffCSS provides two utilities for complex styles: <code>style</code> and <code>theme</code>.</p>
<p><strong>style</strong> creates arbitrary styles and returns an empty string resolver that injects them on the first usage. Inside <strong>functional forms</strong> you can reference other selectors (tokens) as nested keys — resolved lazily when the rule is used:</p>
<pre><code class="language-ts">const btn = className({ color: 'white' });
const themed = style(() =&gt; ({
    body: {
        color: accent(),
        ['&amp; &gt; ' + btn]: { marginTop: '1rem' } // `&amp; &gt; .f_6-0`
    }
}));
themed(); // ''
</code></pre>
<p>Because the rule is wrapped in a function, the token <code>btn</code> is resolved only at the moment of materialization — keeping the styles lazy and avoiding module-level side effects.</p>
<p><strong>theme</strong> groups variables and their values into theme variants to simplify dynamic style switching. It returns a controller with <code>vars</code>, <code>list</code>, <code>get</code> and <code>set</code>:</p>
<pre><code class="language-ts">const myTheme = theme({
    vars: { bg: '#eff' },
    options: { light: { bg: '#eff' }, dark: { bg: '#111' } },
    initial: 'light'
});
myTheme.get(); // 'light'
myTheme.set('dark'); // true
</code></pre>
<h2>Isolated style contexts</h2>
<p><code>context()</code> creates an isolated style generation context with its own key, counters and stylesheets, so tokens never collide across contexts:</p>
<pre><code class="language-ts">import { context } from 'effcss';

const ctx = context({ key: 'app' });
ctx.className({ color: 'white' })(); // 'app_6-0'
ctx.clear(); // wipes styles, keeps the context usable
ctx.dispose(); // permanently destroys the context
</code></pre>
<p>Each context exposes the full set of utilities (except <code>context</code> and <code>getContexts</code>) plus explicit lifecycle management (<code>clear</code>, <code>dispose</code>, <code>Symbol.dispose</code> for <code>using</code>). <code>getContexts()</code> lists active contexts, excluding disposed ones via weak references — ideal for isolating styles between micro-frontends.</p>
<h2>SSR is kept</h2>
<p>SSR support is preserved. Every context — the global one and each isolated one — can be serialized into <code>&lt;style&gt;</code> tags and reused on the client:</p>
<pre><code class="language-ts">import { className, serialize } from 'effcss';

className({ color: 'white' })();
const html = serialize();
// '&lt;style data-effcss-key="f-6" data-effcss-count="1"&gt;.f_6-0 { color: white; }&lt;/style&gt;'
</code></pre>
<p>Isolated contexts serialize independently via <code>ctx.serialize()</code>, so micro-frontends can prepare and hydrate their own styles without collisions.</p>
<h2>Who is it for</h2>
<ul>
<li><strong>Design-system and component libraries</strong> — typed, isolated selectors with no style leaks.</li>
<li><strong>Micro-frontends / multi-team setups</strong> — each context is fully isolated and independently serializable.</li>
<li><strong>SSR/SSG projects</strong> — styles are prepared on the server and hydrated on the client without FOUC.</li>
<li><strong>Anyone who wants a small, lazy, tree-shakeable CSS-in-TS</strong> — the whole library is ~7 KB.</li>
</ul>
<p>If you need a build-time zero-runtime solution (Linaria, vanilla-extract, CSS-Zero) or are already deep in Tailwind, EffCSS is a different paradigm — but it can coexist with both.</p>
<h2>Final thoughts</h2>
<p>EffCSS v6 is a leaner, more confident library. By unifying the API around token resolvers, making everything lazy and tree-shakeable by default, it removes the abstractions that weighed v5 down while keeping the core idea intact — more flexibility with lower costs. Could this be the tool you were looking for?</p>
<p>Enjoy your Frontend Development!</p>
]]></content:encoded></item></channel></rss>