<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: self-dev</title>
    <description>The latest articles on DEV Community by self-dev (@ddtamn).</description>
    <link>https://dev.to/ddtamn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1409685%2F11676a40-d23c-49cc-b236-d90916c017d2.jpeg</url>
      <title>DEV Community: self-dev</title>
      <link>https://dev.to/ddtamn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ddtamn"/>
    <language>en</language>
    <item>
      <title>Why UI Libraries Still Need Explicit CSS Imports</title>
      <dc:creator>self-dev</dc:creator>
      <pubDate>Thu, 07 May 2026 20:30:31 +0000</pubDate>
      <link>https://dev.to/ddtamn/why-ui-libraries-still-need-explicit-css-imports-5b6o</link>
      <guid>https://dev.to/ddtamn/why-ui-libraries-still-need-explicit-css-imports-5b6o</guid>
      <description>&lt;p&gt;While building a UI library for SvelteKit, I wanted the consumer setup to feel as simple as possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@svkit/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No extra CSS imports.&lt;br&gt;
No Tailwind setup.&lt;br&gt;
No configuration.&lt;br&gt;
Just import the component and everything works.&lt;/p&gt;

&lt;p&gt;At first, this sounded straightforward.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;After several experiments, I ended up discovering something much deeper than a styling issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CSS is not really part of the JavaScript module system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And modern bundlers only make it &lt;em&gt;feel&lt;/em&gt; like it is.&lt;/p&gt;


&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;I'm currently building &lt;strong&gt;svkit&lt;/strong&gt; (coming soon :)), a Svelte component library primarily built for my own projects and experiments around component architecture, styling systems, and developer experience.&lt;/p&gt;

&lt;p&gt;The library is split into two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@svkit/ui
@svkit/styles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@svkit/ui&lt;/code&gt; contains the Svelte components and logic.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@svkit/styles&lt;/code&gt; contains the design tokens, themes, and component styles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Internally, the styling system uses Tailwind utilities and &lt;code&gt;@apply&lt;/code&gt;, but the goal is to distribute fully compiled CSS so consumers don't need Tailwind at all.&lt;/p&gt;

&lt;p&gt;The ideal setup I wanted was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@svkit/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without requiring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'@svkit/styles'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one extra CSS import felt unnecessary.&lt;/p&gt;

&lt;p&gt;So I started experimenting.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Intuitive Solution Everyone Tries
&lt;/h2&gt;

&lt;p&gt;The most obvious approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works perfectly inside app source files.&lt;/p&gt;

&lt;p&gt;For example, inside a Svelte component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./button.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vite intercepts the CSS import, transforms it, injects styles correctly, and everything works.&lt;/p&gt;

&lt;p&gt;So naturally, I expected the same thing to work inside a published package.&lt;/p&gt;

&lt;p&gt;But crossing package boundaries changes everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 1 — Static CSS Imports
&lt;/h2&gt;

&lt;p&gt;I tried adding a static CSS import directly inside the distributed JS entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/styles/dist/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client (Vite dev/build)&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR (Node.js ESM)&lt;/td&gt;
&lt;td&gt;Crashes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The SSR error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first this was confusing.&lt;/p&gt;

&lt;p&gt;Why does CSS importing work perfectly inside app source files, but fail inside a package?&lt;/p&gt;

&lt;p&gt;The answer is subtle.&lt;/p&gt;

&lt;p&gt;Vite only transforms CSS imports inside source files that belong to the application's module graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App Source Graph
    ↓
Vite owns the transform pipeline
    ↓
CSS imports work

Package Export Boundary
    ↓
Node.js sees raw .css imports
    ↓
SSR crashes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once a file is resolved through &lt;code&gt;package.json&lt;/code&gt; exports, Vite treats it as a prebuilt dependency.&lt;/p&gt;

&lt;p&gt;That means this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is eventually seen directly by Node.js during SSR.&lt;/p&gt;

&lt;p&gt;And Node.js does not understand CSS as an ESM module.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 2 — Dynamic &lt;code&gt;import()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The next idea was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoided the SSR crash because the import never executes on the server.&lt;/p&gt;

&lt;p&gt;Problem solved?&lt;/p&gt;

&lt;p&gt;Not really.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Massive FOUC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The issue is that dynamic &lt;code&gt;import()&lt;/code&gt; is asynchronous.&lt;/p&gt;

&lt;p&gt;The browser renders the page before the CSS finishes loading.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unstyled components during hydration&lt;/li&gt;
&lt;li&gt;visible layout shift&lt;/li&gt;
&lt;li&gt;5–10 seconds of Flash of Unstyled Content in some cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technically functional.&lt;/p&gt;

&lt;p&gt;Architecturally terrible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 3 — Precompiled Tailwind CSS
&lt;/h2&gt;

&lt;p&gt;At this point, I thought maybe Tailwind was the real problem.&lt;/p&gt;

&lt;p&gt;What surprised me most was that the issue had nothing to do with Tailwind itself.&lt;/p&gt;

&lt;p&gt;Even after fully compiling the CSS into plain output, the exact same architectural problem remained.&lt;/p&gt;

&lt;p&gt;Since the styles used &lt;code&gt;@apply&lt;/code&gt;, I tried fully compiling everything during build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @tailwindcss/cli &lt;span class="nt"&gt;--input&lt;/span&gt; compile-entry.css &lt;span class="nt"&gt;--output&lt;/span&gt; dist/styles.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated fully resolved CSS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no &lt;code&gt;@apply&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;no Tailwind dependency&lt;/li&gt;
&lt;li&gt;plain CSS output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final file was around 133KB.&lt;/p&gt;

&lt;p&gt;But the problem still remained.&lt;/p&gt;

&lt;p&gt;Because the issue was never Tailwind.&lt;/p&gt;

&lt;p&gt;The issue was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript importing CSS across package boundaries during SSR.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From Node.js's perspective, precompiled CSS is still just a &lt;code&gt;.css&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;And Node.js still cannot execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside an SSR dependency graph.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 4 — Inlining CSS into JavaScript
&lt;/h2&gt;

&lt;p&gt;Another approach was serializing the CSS directly into JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/* compiled CSS */`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then injecting it at runtime.&lt;/p&gt;

&lt;p&gt;Technically possible.&lt;/p&gt;

&lt;p&gt;But the trade-offs were ugly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;huge JS payloads&lt;/li&gt;
&lt;li&gt;duplicated CSS across components&lt;/li&gt;
&lt;li&gt;CSP concerns&lt;/li&gt;
&lt;li&gt;runtime style injection&lt;/li&gt;
&lt;li&gt;loss of caching efficiency&lt;/li&gt;
&lt;li&gt;mixing styling concerns into JS runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It felt like fighting the platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experiment 5 — A Custom Vite Plugin
&lt;/h2&gt;

&lt;p&gt;I also considered creating a custom Vite plugin that would intercept imports and automatically inject CSS alongside components.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@svkit/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would internally transform into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@svkit/styles/button.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@svkit/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was probably the closest thing to the original dream.&lt;/p&gt;

&lt;p&gt;But it introduced a new category of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plugin maintenance burden&lt;/li&gt;
&lt;li&gt;SSR adapter edge cases&lt;/li&gt;
&lt;li&gt;coupling consumers to Vite&lt;/li&gt;
&lt;li&gt;ecosystem fragility&lt;/li&gt;
&lt;li&gt;framework compatibility issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the complexity no longer felt justified.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Realization
&lt;/h2&gt;

&lt;p&gt;Eventually, I realized something important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CSS is not actually part of the JavaScript module system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modern bundlers only create the illusion that it is.&lt;/p&gt;

&lt;p&gt;When you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./foo.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside app source code, Vite transforms it for you.&lt;/p&gt;

&lt;p&gt;But that behavior is not native JavaScript.&lt;/p&gt;

&lt;p&gt;It's bundler-controlled behavior.&lt;/p&gt;

&lt;p&gt;And once package boundaries, SSR runtimes, and prebuilt dependencies enter the picture, the illusion starts to break.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Explicit CSS Imports Still Exist
&lt;/h2&gt;

&lt;p&gt;At this point, I started looking at how mature UI libraries handle styles.&lt;/p&gt;

&lt;p&gt;And almost all of them settled on the same pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'@svkit/styles'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap/dist/bootstrap.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'daisyui'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not because library authors are lazy.&lt;/p&gt;

&lt;p&gt;But because explicit CSS imports are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSR-safe&lt;/li&gt;
&lt;li&gt;synchronous&lt;/li&gt;
&lt;li&gt;predictable&lt;/li&gt;
&lt;li&gt;cache-friendly&lt;/li&gt;
&lt;li&gt;bundler-agnostic&lt;/li&gt;
&lt;li&gt;framework-agnostic&lt;/li&gt;
&lt;li&gt;easy to reason about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And most importantly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;they align with how the platform actually works.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Architecture I Ended Up Choosing
&lt;/h2&gt;

&lt;p&gt;Instead of hiding CSS behind JavaScript imports, svkit now uses an explicit style entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* app.css */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'@svkit/styles'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And components are imported normally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@svkit/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's one extra line.&lt;/p&gt;

&lt;p&gt;But in return, the architecture becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable&lt;/li&gt;
&lt;li&gt;SSR-safe&lt;/li&gt;
&lt;li&gt;predictable&lt;/li&gt;
&lt;li&gt;maintainable&lt;/li&gt;
&lt;li&gt;adapter-safe&lt;/li&gt;
&lt;li&gt;framework-independent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly, after all the experiments, it feels like the correct trade-off.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;One of the most interesting things about modern frontend tooling is how much complexity can hide behind seemingly simple developer experience goals.&lt;/p&gt;

&lt;p&gt;"Just import the component and let the styles load automatically" sounds trivial.&lt;/p&gt;

&lt;p&gt;But underneath that sentence are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESM semantics&lt;/li&gt;
&lt;li&gt;SSR runtimes&lt;/li&gt;
&lt;li&gt;asset pipelines&lt;/li&gt;
&lt;li&gt;bundler ownership&lt;/li&gt;
&lt;li&gt;synchronous rendering constraints&lt;/li&gt;
&lt;li&gt;hydration timing&lt;/li&gt;
&lt;li&gt;dependency externalization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deeper I explored this problem, the more I realized:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;modern bundlers do not own the entire runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And CSS still behaves fundamentally differently from JavaScript.&lt;/p&gt;

&lt;p&gt;The web platform has always treated CSS and JavaScript as fundamentally different systems.&lt;/p&gt;

&lt;p&gt;Modern tooling can blur that boundary for a while.&lt;/p&gt;

&lt;p&gt;But eventually, package boundaries, SSR runtimes, and real-world distribution models expose the difference again.&lt;/p&gt;

&lt;p&gt;Which is probably why, even in 2026, UI libraries still need explicit CSS imports.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>svelte</category>
      <category>vite</category>
      <category>css</category>
    </item>
    <item>
      <title>I Ported audio-ui (shadcn / React) to Svelte — Here’s What I Learned</title>
      <dc:creator>self-dev</dc:creator>
      <pubDate>Wed, 01 Apr 2026 06:07:12 +0000</pubDate>
      <link>https://dev.to/ddtamn/i-ported-audio-ui-shadcn-react-to-svelte-heres-what-i-learned-5de7</link>
      <guid>https://dev.to/ddtamn/i-ported-audio-ui-shadcn-react-to-svelte-heres-what-i-learned-5de7</guid>
      <description>&lt;p&gt;Recently, I’ve been exploring how far the &lt;strong&gt;headless + composable UI philosophy&lt;/strong&gt; can go outside the React ecosystem.&lt;/p&gt;

&lt;p&gt;So I decided to take on a small challenge:&lt;br&gt;
👉 porting &lt;strong&gt;audio-ui&lt;/strong&gt; (originally built for shadcn / React) into Svelte.&lt;/p&gt;

&lt;p&gt;Demo: &lt;a href="https://svelte-audio-ui.vercel.app/" rel="noopener noreferrer"&gt;svelte-audio-ui&lt;/a&gt;&lt;br&gt;
Github : &lt;a href="https://github.com/ddtamn/svelte-audio-ui" rel="noopener noreferrer"&gt;github.com/ddtamn/svelte-audio-ui&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Exists
&lt;/h2&gt;

&lt;p&gt;In the React world, tools like shadcn have made it very natural to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;copy components directly into your codebase&lt;/li&gt;
&lt;li&gt;fully control the implementation&lt;/li&gt;
&lt;li&gt;build systems that are easy to extend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But in Svelte, especially for &lt;strong&gt;audio-related UI&lt;/strong&gt;, there aren’t many solutions that follow this approach.&lt;/p&gt;

&lt;p&gt;Most libraries are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;too opinionated&lt;/li&gt;
&lt;li&gt;not composable&lt;/li&gt;
&lt;li&gt;or difficult to extend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the goal here is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bring the same &lt;em&gt;“you own the code”&lt;/em&gt; philosophy into Svelte, specifically for audio UI.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Core Idea
&lt;/h2&gt;

&lt;p&gt;Instead of shipping a package, this approach follows the same pattern as shadcn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No npm dependency&lt;/li&gt;
&lt;li&gt;Components are installed into your project&lt;/li&gt;
&lt;li&gt;You can modify everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the center of it is:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;AudioProvider&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This acts as the core engine that handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;playback state&lt;/li&gt;
&lt;li&gt;queue management&lt;/li&gt;
&lt;li&gt;synchronization between components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else — player, controls, sliders — composes on top of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;AudioPlayer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$lib/components/ui/audio/player&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;AudioPlayer.Root&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;AudioPlayer.ControlBar&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AudioPlayer.Play&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AudioPlayer.SeekBar&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AudioPlayer.Volume&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/AudioPlayer.ControlBar&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/AudioPlayer.Root&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;swap components easily&lt;/li&gt;
&lt;li&gt;extend behavior&lt;/li&gt;
&lt;li&gt;or build your own UI layer entirely&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Built for the Svelte Way
&lt;/h2&gt;

&lt;p&gt;Porting wasn’t about copying code.&lt;/p&gt;

&lt;p&gt;The goal was to make it feel &lt;strong&gt;idiomatic in Svelte&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;leveraging reactivity instead of hooks&lt;/li&gt;
&lt;li&gt;simplifying state flow&lt;/li&gt;
&lt;li&gt;keeping components lightweight&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  AI-Ready Documentation
&lt;/h2&gt;

&lt;p&gt;One interesting aspect of this project is how it works with AI tools.&lt;/p&gt;

&lt;p&gt;Since everything lives inside your codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structure is predictable&lt;/li&gt;
&lt;li&gt;patterns are consistent&lt;/li&gt;
&lt;li&gt;components are easy to extend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs also include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it easier for AI tools to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understand the system&lt;/li&gt;
&lt;li&gt;generate new components&lt;/li&gt;
&lt;li&gt;help you iterate faster&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Extras: Particles
&lt;/h2&gt;

&lt;p&gt;There’s also a &lt;strong&gt;particles section&lt;/strong&gt; for visual experiments.&lt;/p&gt;

&lt;p&gt;It’s not core to the system, but it’s fun to explore and can be used for landing pages or creative UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;This is still early.&lt;/p&gt;

&lt;p&gt;Next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refine API to feel more “Svelte-native”&lt;/li&gt;
&lt;li&gt;improve developer experience&lt;/li&gt;
&lt;li&gt;add more components as needed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Open source is interesting like this.&lt;/p&gt;

&lt;p&gt;You don’t always need to reinvent ideas — sometimes you just need to &lt;strong&gt;translate them properly into another ecosystem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’re using Svelte:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;would this be useful for you?&lt;/li&gt;
&lt;li&gt;what kind of audio components do you usually need?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would love to hear your thoughts 🙏&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>svelte</category>
      <category>shadcn</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
