Skip to main content

The shadcn/ui effect: why unstyled components win

Explore why the unstyled component model pioneered by shadcn/ui is reshaping React development, and what it means for product tours and onboarding.

DomiDex
DomiDexCreator of Tour Kit
April 11, 20269 min read
Share
The shadcn/ui effect: why unstyled components win

The shadcn/ui effect: why unstyled components win

Something shifted in React's ecosystem around 2023, and most developers felt it before they could name it. shadcn/ui hit 104,000 GitHub stars and 560,000 weekly npm downloads by early 2026. Those numbers rival established frameworks, not component libraries. But the interesting part isn't the adoption curve. It's what shadcn/ui revealed about how developers actually want to build.

The unstyled component model won. Not because it's newer, but because it solves a real problem that styled libraries papered over for years: who owns the code?

If you're building product tours, onboarding flows, or any interactive UI layer, this question matters more than you'd think.

npm install @tourkit/core @tourkit/react

The problem: styled libraries stopped scaling

For most of the 2010s, the pitch was simple. Install a component library, import a <Button>, ship it. Material UI (51,000 npm weekly downloads at its 2019 peak), Ant Design, Chakra UI: they all followed the same model. You got components with opinions about color, spacing, typography, and animation baked in.

That worked when teams shipped one app with one design system. It stopped working when three things happened at once.

First, design systems diverged. Every serious product team now maintains its own tokens, and wrapping a styled library's opinions in overrides produces !important chains, theme object gymnastics, and CSS specificity fights that nobody wins. Smashing Magazine put it well: frameworks create a translation layer between what you want and what you get.

Second, React Server Components killed CSS-in-JS at runtime. styled-components and Emotion relied on client-side style injection, which added 12-15KB of runtime overhead per bundle. When the rendering model moved server-first, those libraries couldn't follow without breaking hydration. The LogRocket blog documented this shift: teams moved from CSS-in-JS to Tailwind + headless primitives because the alternative was fighting the framework.

Third, AI code generation exposed a hidden cost. When you ask an AI agent to modify a component from node_modules, it can't. The code sits behind an abstraction wall. Tony Dinh, creator of TypingMind, called it directly: "shadcn is the default UI lib of LLMs" (RedMonk, 2025). As of 2026, Bolt, Lovable, Vercel v0, and Replit all default to shadcn/ui. Transparent source code that lives in your project is readable, modifiable, and composable by both humans and machines.

The argument: ownership is the feature

shadcn/ui's official docs are blunt about this: "This is NOT a component library. It's a collection of re-usable components that you can copy and paste into your apps." That distinction matters more than it sounds.

The traditional model creates a dependency graph. You install a package, import components, and hope the library's API surface covers your use case. When it doesn't (and it always doesn't), you fork, wrap, or hack around the limitation. Every workaround adds coupling to a dependency you don't control.

The unstyled model inverts this. You get behavior primitives (focus management, keyboard navigation, ARIA attributes) from a headless layer, then compose your own UI on top. The behavior is the hard part. Styling? Yours.

As of April 2026, the headless ecosystem is mature enough to back this claim with numbers:

LibraryComponentsWeekly downloadsFramework support
Radix UI Primitives30+9.1M+React
React Aria (Adobe)50+2M+React
Base UI (MUI)35Growing (v1.0 Feb 2026)React
Ark UI (Chakra)45+GrowingReact, Solid, Vue, Svelte
Headless UI (Tailwind Labs)101.5M+React, Vue

Radix alone pulls 9.1 million weekly downloads (Subframe, 2024). MUI released Base UI 1.0 in February 2026 with 35 accessible components (InfoQ, 2026). Adobe's React Aria ships 50+ components with i18n for 30+ languages. Combined, these five headless libraries account for over 14 million weekly npm installs. Not a niche pattern. The default.

Supabase's auth UI migration is the clearest real-world validation. Ivan Vasilov explained why they moved to shadcn/ui's model: "The main reason shadcn approach is good for us is that it transfers the ownership of the code to the user" (RedMonk, 2025). Their npm-based auth UI had become, in his words, "an endless customization wishlist and maintenance burden."

The counterargument: unstyled isn't free

Fairness requires acknowledging the tradeoffs.

Unstyled components push more work onto you. A team using Chakra UI or Ant Design gets a cohesive visual system out of the box with 80+ pre-styled components. Building that same visual consistency from headless primitives takes real effort: you're writing the Popover styles, the Dialog animations, the Tooltip positioning logic. For small teams shipping a prototype in two weeks, that overhead is real.

There's also a learning curve. Understanding how to compose Radix's asChild pattern, or how React Aria's 50+ hooks map to rendered output, takes time that installing Material UI doesn't. Headless documentation tends to be more abstract because the libraries themselves are more abstract.

And the "copy-paste" model has a versioning gap. When shadcn ships a bug fix to its Dialog component, you don't get it automatically. You re-run the CLI or manually diff the changes. Traditional packages handle this through semver and npm update. Whether that tradeoff is worth it depends on how much you've customized the component. If you've changed 40 lines, an npm update would overwrite your work anyway.

These are genuine costs. But they scale well. The overhead of building your own component layer is front-loaded. The overhead of fighting a styled library's opinions? That compounds every sprint.

What this means for product tours and onboarding

Here's where the unstyled component argument gets specific, and where I have skin in the game. (I built Tour Kit, so read this section with appropriate skepticism.)

Most product tour libraries followed the old model. React Joyride ships at 37KB gzipped with its own tooltip styles. Shepherd.js adds 28KB with AGPL licensing restrictions. Intro.js bundles 30KB of opinionated step UI. You're expected to accept those defaults or override them. The CSS specificity fights are the same ones that plagued styled component libraries, just applied to a narrower domain.

The shadcn/ui pattern maps directly to onboarding. Separate the behavior (step sequencing, element targeting, scroll handling, focus management, ARIA live regions) from the presentation (how your tooltip looks, where your progress indicator sits, what animation plays between steps). Behavior is genuinely hard to build well. Styling should match your app, not a third-party library's defaults.

Here's what that looks like in practice:

// src/components/tour/CustomStep.tsx
import { useTour, useStep } from '@tourkit/react';

function CustomStep() {
  const { currentStep, next, prev, stop } = useTour();
  const { targetRef, content } = useStep();

  // Your design system. Your animation library.
  // Your Tailwind classes. Your rules.
  return (
    <div className="rounded-lg border bg-card p-4 shadow-md">
      <p className="text-sm text-muted-foreground">{content}</p>
      <div className="mt-3 flex gap-2">
        <button onClick={prev} className="btn-ghost">Back</button>
        <button onClick={next} className="btn-primary">Next</button>
      </div>
    </div>
  );
}

No CSS overrides. No !important. No "how do I change the tooltip arrow color" Stack Overflow questions. The behavior hooks handle positioning, keyboard navigation, and accessibility. You handle how it looks.

Tour Kit isn't the only library thinking this way. The trend toward headless architecture is showing up across the onboarding space. But it's the specific problem we designed around: core logic under 8KB gzipped (4.6x smaller than React Joyride), zero runtime dependencies, and a composition model where you install only the packages you need across 10 packages.

The limitation is real: there's no visual builder. You need developers comfortable writing React 18+. For teams that want drag-and-drop tour creation, Appcues ($299/month) or Userpilot ($249/month) still make more sense. Headless means you write more JSX, but you get components that belong to your design system instead of fighting against one.

What we'd do differently

If you're evaluating how the unstyled component trend affects your stack, here's a practical framework.

For generic UI (buttons, dialogs, dropdowns): Pick a headless primitive layer. Radix, React Aria, or Base UI are all production-ready in 2026. If you want a head start, shadcn/ui gives you Radix + Tailwind compositions you can customize. Settled territory.

For domain-specific UI (tours, onboarding, notifications, surveys): Apply the same principle. Separate behavior from presentation. Look for libraries that give you hooks and logic without prescribing how the result looks. If the library ships a CSS file you're expected to include, that's a signal it was designed in the old model.

For AI-assisted development: Prefer code you own over code in node_modules. Transparent source code is the only kind AI can meaningfully edit. If your onboarding layer lives behind an npm abstraction, your AI tools can't help you customize it.

The architectural pattern is the same in every case: behavior as a dependency, presentation as owned code. shadcn/ui proved this works for buttons and dialogs. It works for product tours too.

Get started with Tour Kit at usertourkit.com or explore the code on GitHub.

npm install @tourkit/core @tourkit/react

FAQ

Is shadcn/ui actually unstyled?

shadcn/ui sits between fully unstyled and fully styled. It uses Radix UI primitives for behavior and adds Tailwind CSS for presentation. The key difference is ownership: code copies into your project, so you modify Tailwind classes directly instead of overriding a package's styles. As of April 2026, shadcn/ui supports both Radix UI and Base UI as primitive layers.

What headless UI library should I use in 2026?

Radix UI Primitives lead React adoption with 9.1 million weekly npm downloads. React Aria from Adobe offers the broadest accessibility coverage with 50+ components and 30+ language i18n. Base UI 1.0 (February 2026) ships 35 accessible components from the MUI team. Ark UI covers React, Solid, Vue, and Svelte for cross-framework needs.

How does the unstyled component trend affect product tours?

Traditional tour libraries like React Joyride ship 37KB of opinionated styles that clash with custom design systems. Headless tour libraries like Tour Kit separate behavior (step sequencing, element targeting, accessibility) from presentation, letting you render tour steps with your own Tailwind classes. The result is onboarding UI that matches your app, not a third-party widget.

Does unstyled mean more work for developers?

Yes, upfront. Building visual consistency from headless primitives takes more initial effort than importing Material UI's 80+ pre-styled components. But the cost structure inverts after month two or three. Styled libraries create compounding override complexity as your design diverges from defaults. Unstyled components front-load the cost with linear scaling afterward.

Can AI tools work with unstyled components better than styled ones?

Significantly better. AI coding tools like Cursor, v0, and Bolt can read and modify source code that lives in your project directory. They can't meaningfully edit code hidden inside node_modules. Tony Dinh called shadcn/ui "the default UI lib of LLMs" because transparent, owned source code is what AI agents need to generate and customize components.


JSON-LD Schema:

{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "The shadcn/ui effect: why unstyled components win",
  "description": "Explore why the unstyled component model pioneered by shadcn/ui is reshaping React development, and what it means for product tours and onboarding.",
  "author": {
    "@type": "Person",
    "name": "DomiDex",
    "url": "https://github.com/DomiDex"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Tour Kit",
    "url": "https://usertourkit.com",
    "logo": {
      "@type": "ImageObject",
      "url": "https://usertourkit.com/logo.png"
    }
  },
  "datePublished": "2026-04-11",
  "dateModified": "2026-04-11",
  "image": "https://usertourkit.com/og-images/shadcn-ui-effect-unstyled-components.png",
  "url": "https://usertourkit.com/blog/shadcn-ui-effect-unstyled-components",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://usertourkit.com/blog/shadcn-ui-effect-unstyled-components"
  },
  "keywords": ["shadcn ui effect unstyled components", "headless ui movement", "unstyled component trend", "headless product tour"],
  "proficiencyLevel": "Intermediate",
  "dependencies": "React 18+, TypeScript 5+",
  "programmingLanguage": {
    "@type": "ComputerLanguage",
    "name": "TypeScript"
  }
}

Internal linking suggestions:

  • Link FROM: what-is-headless-ui-guide-onboarding, what-is-headless-onboarding-library, best-headless-ui-libraries-onboarding
  • Link TO: shadcn-ui-product-tour-tutorial, best-shadcn-ui-compatible-tour-library, composable-tour-library-architecture, product-tour-dead-long-live-headless-tour

Distribution checklist:

  • Dev.to (canonical to usertourkit.com)
  • Hashnode (canonical to usertourkit.com)
  • Reddit r/reactjs (discussion post, not link drop)
  • Hacker News (good fit for opinion/thought leadership)
  • Twitter/X thread

Ready to try userTourKit?

$ pnpm add @tour-kit/react