<?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: yuntaeko</title>
    <description>The latest articles on DEV Community by yuntaeko (@yuntaengtaeng).</description>
    <link>https://dev.to/yuntaengtaeng</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4191615%2F541500bc-f87c-4309-9de1-b6520401c239.png</url>
      <title>DEV Community: yuntaeko</title>
      <link>https://dev.to/yuntaengtaeng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuntaengtaeng"/>
    <language>en</language>
    <item>
      <title>I missed Jetpack Compose's @Preview in React, so I built a Vite plugin for it</title>
      <dc:creator>yuntaeko</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:54:11 +0000</pubDate>
      <link>https://dev.to/yuntaengtaeng/i-missed-jetpack-composes-preview-in-react-so-i-built-a-vite-plugin-for-it-255o</link>
      <guid>https://dev.to/yuntaengtaeng/i-missed-jetpack-composes-preview-in-react-so-i-built-a-vite-plugin-for-it-255o</guid>
      <description>&lt;p&gt;I'm a React developer, and I've written my share of Storybook stories too.&lt;/p&gt;

&lt;p&gt;Still, a lot of my day looked like this: build a component, open the app in the browser, click through to the screen where it shows up, and check whether it looks right. Then change something and do it all again.&lt;/p&gt;

&lt;p&gt;Storybook works best when you build components in it first and then build features on top of them. In practice, feature deadlines often flipped that order for me. The feature shipped first, and the stories got written afterwards in a hurry, if at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I saw Compose
&lt;/h2&gt;

&lt;p&gt;At some point I got to look at an Android project written with Jetpack Compose, and its &lt;code&gt;@Preview&lt;/code&gt; felt like a different world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Preview&lt;/span&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;PrimaryButtonPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;PrimaryButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Save"&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;The syntax was tiny. You write only the cases you care about, right next to the code, and you see them while you work. No separate setup to&lt;br&gt;
keep alive.&lt;/p&gt;

&lt;p&gt;As a React developer, I wanted exactly that. So I picked the setup I use most, React with Vite. I'd also wanted to build a Vite plugin for a&lt;br&gt;
while, and the two ideas met.&lt;/p&gt;

&lt;p&gt;That became &lt;strong&gt;Vitrine&lt;/strong&gt;, and 0.2.0 just came out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9s2llk905qgnbe4fxelt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9s2llk905qgnbe4fxelt.png" alt="The Vitrine gallery with a Button preview, a sidebar of grouped components and a props panel" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  One comment, your own Vite
&lt;/h2&gt;

&lt;p&gt;You add a comment above a component you export:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @preview */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PrimaryButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and open &lt;code&gt;/__vitrine&lt;/code&gt; on the Vite dev server you're already running. The component is there.&lt;/p&gt;

&lt;p&gt;Setup is one plugin next to your React plugin:&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="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&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;@vitejs/plugin-react&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;defineConfig&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="s2"&gt;vite&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="nx"&gt;vitrine&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;vite-plugin-react-vitrine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;vitrine&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;Because the previews go through your own dev server, aliases, CSS, plugins and HMR behave exactly like in your app.&lt;/p&gt;

&lt;p&gt;You can try it without installing anything in the &lt;a href="https://vitrine-4kr.pages.dev/demo/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing only the cases you care about
&lt;/h2&gt;

&lt;p&gt;Like Compose previews, you can add a few named cases when one isn't enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;preview&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="s2"&gt;vite-plugin-react-vitrine/preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;preview&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&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="na"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Danger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;danger&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="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;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjshgqv1is34otyaf7now.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjshgqv1is34otyaf7now.png" alt="Matrix view rendering a button in every combination of tone and size" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gallery also gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Props controls&lt;/strong&gt; generated from your TypeScript prop types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variants&lt;/strong&gt; side by side, and a &lt;strong&gt;Matrix&lt;/strong&gt; that renders every combination of two props&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actions&lt;/strong&gt;, a log of calls to &lt;code&gt;onClick&lt;/code&gt;, &lt;code&gt;onChange&lt;/code&gt; and other &lt;code&gt;on*&lt;/code&gt; props&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Globals&lt;/strong&gt; for values like theme or locale that a provider sets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viewports&lt;/strong&gt; that trigger real media queries, since each preview renders in its own frame&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History&lt;/strong&gt; to compare the current version with an earlier save&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Links&lt;/strong&gt; that reopen the same preview, variant and values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when you want to share the gallery with reviewers, &lt;code&gt;vitrine-plugin-static&lt;/code&gt; builds it into static files. The live demo is built that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next to your editor, if you want
&lt;/h2&gt;

&lt;p&gt;The closest thing to the Compose experience is the optional VS Code extension. It opens the same gallery in a panel beside your code, and as you move your cursor between &lt;code&gt;@preview&lt;/code&gt; exports, the panel switches to that preview. It can also save the props you set in the gallery as a new variant in your file.&lt;/p&gt;

&lt;p&gt;You don't need it, though. The gallery is just a page on your dev server, so it works in any browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vite-plugin-react-vitrine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://vitrine-4kr.pages.dev/demo/" rel="noopener noreferrer"&gt;https://vitrine-4kr.pages.dev/demo/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://vitrine-4kr.pages.dev/docs" rel="noopener noreferrer"&gt;https://vitrine-4kr.pages.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/yuntaengtaeng/vitrine" rel="noopener noreferrer"&gt;https://github.com/yuntaengtaeng/vitrine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It supports React 18 and 19 and Vite 6.4 and later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Vitrine is still 0.x, and I'd love to hear how it fits into your workflow: what breaks in your setup, and what would make it something you reach for every day. If you'd like to contribute, I've opened a few beginner-friendly issues on GitHub.&lt;/p&gt;

&lt;p&gt;I'm from Korea and English isn't my first language, so sorry if anything reads a bit off. Thanks for reading!&lt;/p&gt;

</description>
      <category>react</category>
      <category>vite</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
