Welcome 👋!

When evaluating modern UI libraries, the market is flooded with impressive choices; But not all are built equal. Shadcn UI has earned a following among developers working primarily within the React + Tailwind + Radix ecosystem. However, its design philosophy is inherently narrow. It forces teams into a predefined stack, leaving little room for freedom, modularity, or framework independence. That's where Open SASS comes in, not just as an alternative, but as a next-generation solution built for a truly agnostic, performant, and future-ready development experience. Let's explore why Open SASS doesn't just compete; It obliterates Shadcn UI on every front.

Framework and Styling Agnosticism

One of the most obvious limitations of Shadcn UI is that it is hardwired into the React ecosystem. You can't use it without installing and depending on React, Radix UI, Lucide React, and Tailwind CSS. This is fine if you're working on a Next.js project and never plan to leave that bubble. But what happens when you're building with Vue, Svelte or even a native desktop interface? You're out of luck. Although there are unofficial ShadCN components available for each of these frameworks, they are maintained by the community rather than the core team. Shadcn simply does not operate outside of React.

To see how tightly Shadcn is bound to its stack, try installing a simple Accordion component with the following command:

npx shadcn@latest add accordion

The generated component looks like this:

"use client";

import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";

import { cn } from "@/lib/utils";

function Accordion({
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
  return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
}

function AccordionItem({
  className,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
  return (
    <AccordionPrimitive.Item
      data-slot="accordion-item"
      className={cn("border-b last:border-b-0", className)}
      {...props}
    />
  );
}

function AccordionTrigger({
  className,
  children,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
  return (
    <AccordionPrimitive.Header className="flex">
      <AccordionPrimitive.Trigger
        data-slot="accordion-trigger"
        className={cn(
          "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
          className
        )}
        {...props}
      >
        {children}
        <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  );
}

function AccordionContent({
  className,
  children,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
  return (
    <AccordionPrimitive.Content
      data-slot="accordion-content"
      className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
      {...props}
    >
      <div className={cn("pt-0 pb-4", className)}>{children}</div>
    </AccordionPrimitive.Content>
  );
}

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

This code is riddled with dependencies: @radix-ui/react-accordion, lucide-react, Tailwind utilities, and React's own component system. Want to use it in a Vue app? Too bad. Trying to reuse it in a Yew project written in Rust? Forget about it. You're locked in, hard.

In contrast, Open SASS components are the very definition of framework-agnostic. Each component in Open SASS is shipped in multiple flavors, tailored to the platform you're using. Want to use the Accordion in Yew? Run, after installing the Open SASS CLI:

os add accordion-rs yew

This fetches only the Rust/Yew-compatible source code with no extra fluff, no unnecessary dependencies, and no styling assumptions. Here's an excerpt of what you get:

use crate::common::{Align, Size};
use yew::prelude::*;

#[derive(Properties, Clone, PartialEq)]
pub struct AccordionProps {
    pub expand: UseStateHandle<bool>,

    #[prop_or_default]
    pub expanded: Html,

    #[prop_or_default]
    pub collapsed: Html,

    #[prop_or_default]
    pub children: ChildrenWithProps<List>,

    #[prop_or_default]
    pub size: Size,

    #[prop_or_default]
    pub aria_controls: &'static str,

    #[prop_or_default]
    pub style: &'static str,

    #[prop_or_default]
    pub expanded_style: &'static str,

    #[prop_or_default]
    pub collapsed_style: &'static str,

    #[prop_or_default]
    pub content_style: &'static str,

    #[prop_or_default]
    pub class: &'static str,

    #[prop_or_default]
    pub expanded_class: &'static str,

    #[prop_or_default]
    pub collapsed_class: &'static str,

    #[prop_or_default]
    pub content_class: &'static str,

    #[prop_or(true)]
    pub aria_enabled: bool,

    #[prop_or(600)]
    pub duration: u64,

    #[prop_or_default]
    pub will_open: Callback<()>,

    #[prop_or_default]
    pub did_open: Callback<()>,

    #[prop_or_default]
    pub will_close: Callback<()>,

    #[prop_or_default]
    pub did_close: Callback<()>,
}

#[function_component]
pub fn Accordion(props: &AccordionProps) -> Html {
    let is_expanded = &props.expand;
    let is_expanded_value = **is_expanded;

    let toggle_expansion = {
        let is_expanded = is_expanded.clone();
        let props = props.clone();

        move |e: MouseEvent| {
            e.prevent_default();

            if is_expanded_value {
                props.will_close.emit(());
                is_expanded.set(false);
                props.did_close.emit(());
            } else {
                props.will_open.emit(());
                is_expanded.set(true);
                props.did_open.emit(());
            }
        }
    };

    html! {
        <div
            style={format!(
                "{} {}",
                props.size.to_style(),
                props.style
            )}
            class={props.class}
        >
            <div
                aria-expanded={if props.aria_enabled { Some(is_expanded_value.to_string()) } else { None }}
                aria-controls={if props.aria_enabled { Some(props.aria_controls) } else { None }}
                onclick={toggle_expansion.clone()}
                class={if is_expanded_value {
                        props.expanded_class
                    } else {
                        props.collapsed_class
                    }}
                style={format!(
                    "cursor: pointer; transition: all {}ms; {}",
                    props.duration,
                    if is_expanded_value {
                        props.expanded_style
                    } else {
                        props.collapsed_style
                    }
                )}
            >
                { if is_expanded_value { props.expanded.clone() } else { props.collapsed.clone() } }
            div>
            { if is_expanded_value {
                html! {
                    <div
                        id={props.aria_controls}
                        class={props.content_class}
                        style={format!(
                            "overflow: hidden; transition: all {}ms; {}",
                            props.duration,
                            props.content_style
                        )}
                    >
                    { for props.children.iter() }
                    div>
                }
            } else {
                html! {}
            } }
        div>
    }
}

#[derive(Clone, PartialEq, Properties)]
pub struct ItemProps {
    #[prop_or_default]
    pub children: Html,

    #[prop_or_default]
    pub style: &'static str,

    #[prop_or_default]
    pub class: &'static str,

    #[prop_or_default]
    pub align: Align,

    #[prop_or_default]
    pub title: &'static str,

    #[prop_or_default]
    pub icon: &'static str,
}

#[function_component]
pub fn Item(props: &ItemProps) -> Html {
    html! {
        <li
            class={props.class}
            style={format!(
                "{} {}",
                props.align.to_style(),
                props.style
            )}
        >
            { if !props.icon.is_empty() {
                    html! { <span class="mr-2">{ props.icon }span> }
                } else {
                    html! {}
                } }
            { if !props.title.is_empty() {
                    html! { <strong>{ props.title }strong> }
                } else {
                    html! {}
                } }
            { props.children.clone() }
        li>
    }
}

#[derive(Clone, PartialEq, Properties)]
pub struct ButtonProps {
    #[prop_or_default]
    pub children: Html,

    #[prop_or_default]
    pub style: &'static str,

    #[prop_or_default]
    pub class: &'static str,
}

#[function_component]
pub fn Button(props: &ButtonProps) -> Html {
    html! {
        <button class={props.class} style={props.style}>
            { props.children.clone() }
        button>
    }
}

#[derive(Clone, PartialEq, Properties)]
pub struct ListProps {
    #[prop_or_default]
    pub children: Children,

    #[prop_or_default]
    pub style: &'static str,

    #[prop_or_default]
    pub class: &'static str,
}

#[function_component]
pub fn List(props: &ListProps) -> Html {
    html! {
        <ul class={props.class} style={props.style}>
            { for props.children.iter() }
        ul>
    }
}

No framework or CSS library is forced on you. Use Tailwind, Bootstrap, Vanilla CSS, or inline styles; Open SASS doesn't care. The component is just logic and structure, not opinionated about how you present it. Want to switch from Yew to Dioxus? Easy:

os add accordion-rs dio

This modularity empowers teams to build components once and reuse them across frontend frameworks, native apps, and even WebAssembly runtimes. This kind of adaptability is simply unachievable with Shadcn UI.

Performance

In software, speed isn't everything; But when tooling begins to slow down your workflow, it becomes a bottleneck. Shadcn UI relies on a Node.js-based CLI to fetch and generate components. That means it needs to spawn a Node process, resolve dependencies, and scaffold code in a relatively bloated JavaScript environment.

Here's what happens when you run Shadcn's CLI:

❯ time yes | npx shadcn@latest add accordion
npx shadcn@latest add accordion  8.98s user 4.85s system 90% cpu 15.279 total

Almost 9 seconds in user time to add a single component. That may not sound like much on its own, but in large projects or CI/CD pipelines, those seconds add up.

Now compare that to Open SASS:

❯ time os add accordion-rs yew
os add accordion-rs yew  0.16s user 0.02s system 5% cpu 0.2 total

Under 0.2 seconds of actual user time. That's roughly ~50x faster, ~15× less CPU usage. The reason? Open SASS is built using native Rust, compiled ahead-of-time to a single binary, and it makes blazing fast HTTP requests directly to crates.io. There's no JavaScript runtime bloat, no npm resolution latency, and no unnecessary complexity.

This level of performance doesn't just make your dev life smoother; It scales beautifully in automated systems, remote dev containers, CI/CD pipelines, and embedded developer tooling. It's the kind of raw speed you feel instantly, and once you've experienced it, you'll never want to go back to a sluggish Node-based CLI.

Customization and Extensibility

Shadcn UI gives you a component that looks nice; If you stay inside its carefully crafted box. It assumes you want Tailwind. It assumes you want React. It assumes you want Radix. If you try to diverge, maybe to add some accessibility behavior that Radix doesn't support out-of-the-box; You'll find yourself fighting the abstraction more than working with it.

Let's say you want to change the accordion animation or use a different icon system in Shadcn. You're diving into a web of dependencies like @radix-ui/react-accordion, needing to override Radix's animations, swap Lucide icons, and massage Tailwind classes around React's className hell.

Open SASS, on the other hand, is fully declarative and extensible. You can:

  • Customize animation speed with a duration prop
  • Inject callbacks (will_open, did_open, will_close, did_close)
  • Apply your own ARIA logic
  • Use class-based or style-based theming
  • Extend components like Item, Button, or List with your own elements

No wrappers. No monkey patching. No awkward re-exports. Just clean, readable components, documented in detail, with no styling or framework assumptions. You're the boss.

Want a bootstrap-themed accordion with your own icons and custom transition logic? You don't have to hack around someone else's component; You build directly with Open SASS:

<Accordion
    expand={expand}
    expanded={html! { <div>{"Details visible"}div> }}
    collapsed={html! { <div>{"Click to expand"}div> }}
    class="accordion-container"
    style="background: #fff; border: 1px solid #ccc;"
    expanded_style="background: #eef;"
    collapsed_style="background: #fee;"
    duration={300}
/>

No layers of indirection. No theme providers. Just clean code.

Conclusion

In every measurable way, portability, performance, customization, extensibility, and cross-platform support, Open SASS obliterates Shadcn UI. Shadcn is a slick choice if you're building yet another Tailwind/React site. But the moment you need to step outside that bubble, whether it's for performance, flexibility, native development, or Rust integration, Shadcn breaks. Open SASS thrives.

Open SASS isn't just an alternative, it's the future of component libraries. Cross-framework. Cross-language (TODO). Ultra-fast. Infinitely extensible.

If you want freedom, speed, and true composability; Open SASS isn't just better.

It's on a whole other level.

GitHub logo opensass / cli

🧩 A CLI to interact with the OpenSASS ecosystem.

🧩 OpenSASS

Crates.io Crates.io Downloads Crates.io License made-with-rust Rust Maintenance

Join our Discord

logo

🐧 Linux (Recommended) 🪟 Windows
cargo install opensass cargo install opensass
Download Executable File Download .exe File
os -h os -h

🧩 OpenSASS: A CLI tool for downloading reusable OpenSASS components from crates.io and integrating them into your WASM frontend projects.

📖 Table of Contents

🚀 Installation

To install the CLI:

cargo install opensass
Enter fullscreen mode Exit fullscreen mode

Or build from source:

git clone https://github.com/opensass/cli.git
cd cli
cargo build --release
Enter fullscreen mode Exit fullscreen mode

✨ Features

  • 🧩 Add OpenSASS component-based crates with a single command.
  • ⚙ Automatically updates Cargo.toml with proper features.
  • 🔁 Copies only the src/ files related to the specified feature.

💡 Usage

os-yew.mp4

Yew Usage

os add i18nrs yew
Enter fullscreen mode Exit fullscreen mode

This will:

  • Download the i18nrs crate.
  • Extract files only related to the yew feature.
  • Copy src/ files into your project under a new directory crate_name, in this case i18nrs.
  • Update your Cargo.toml dependencies and…

We are Open SASS, babe!.

We're working tirelessly on making Rust web development extremely easy for everyone.

If you made it this far, it would be nice if you could join us on Discord.

Together, let's move the web beyond JavaScript, and into something that actually compiles.

Feel free to share this. Fork it. Turn it into a meme. Tattoo it on your CI pipeline. Tell your manager Rust is your spirit animal now.