English | 中文
SoybeanUI is an elegant, modern, accessible and high-quality UI component library with shadcn-like design for Vue 3, built on top of a robust headless foundation. It provides a comprehensive set of accessible, customizable, and performant components.
SoybeanUI is built on a strict two-layer separation model:
┌─────────────────────────────────────────┐
│ @soybeanjs/ui (src/) │
│ S-prefixed components (SButton…) │
│ UnoCSS classes · tailwind-variants │
│ provideXUi(ui) ──────────────────┐ │
└────────────────────────────────────┼────┘
│ style injection
┌────────────────────────────────────▼────┐
│ @soybeanjs/headless (headless/) │
│ Logic · State · A11y · Keyboard nav │
│ useUiContext() reads injected classes │
│ Zero styles — works with any CSS │
└─────────────────────────────────────────┘
| Package | Role | Components |
|---|---|---|
| @soybeanjs/headless | Logic, state, a11y. Zero styles. | 50 primitives, 26 composables |
| @soybeanjs/ui | Styled wrappers. UnoCSS + tv(). |
48 S-prefixed components |
Data flow is strictly one-way: headless → src. The styled layer never imports from headless's internals — it injects style tokens via provideXUi(computedUi) which headless components read through useUiContext().
Some multi-slot headless components also expose Compact aggregators, such as AccordionCompact and TableCompact. They keep item iteration and default content/icon composition inside headless, while the UI layer stays focused on styling and prop forwarding.
Every multi-slot headless component exposes a provide{Name}Ui function. The styled wrapper computes classes using tailwind-variants and injects them:
// In the styled wrapper (src/)
const ui = computed(() =>
mergeSlotVariants(
accordionVariants({ size: props.size }), // tv() output
props.ui, // user overrides
{ root: props.class } // class prop
)
);
provideAccordionUi(ui); // headless reads this via useAccordionUi()ThemeColor— 8 semantic colors:primary·destructive·success·warning·info·carbon·secondary·accentThemeSize— 6 sizes:xs·sm·md·lg·xl·2xl(base 16px atmd)ConfigProvider— sets globaldir,locale,nonce, and defaulttooltipconfig for the entire component treecn()— Tailwind-aware class merge (clsx+tailwind-merge), used for conflict-free class composition
@soybeanjs/headless ships fine-grained sub-paths:
import { AccordionRoot } from '@soybeanjs/headless'; // all components
import { useControllableState } from '@soybeanjs/headless/composables'; // 26 composables
import { transformPropsToContext } from '@soybeanjs/headless/shared'; // pure TS utils
import * as Headless from '@soybeanjs/headless/namespaced'; // namespace object
import type { AccordionUiSlot } from '@soybeanjs/headless/accordion'; // per-component@soybeanjs/ui exports:
import { SButton, SAccordion } from '@soybeanjs/ui'; // all components
import '@soybeanjs/ui/styles.css'; // pre-built UnoCSS stylesheet
// Also: @soybeanjs/ui/nuxt · @soybeanjs/ui/resolverIf you want ready-to-use components with a modern design:
pnpm add @soybeanjs/uiIf you want to build your own design system from scratch:
pnpm add @soybeanjs/headless-
Import Styles
Import the CSS file in your main entry file (e.g.,
main.ts):
import '@soybeanjs/ui/styles.css';-
Global Registration (Optional)
You can register components globally or import them on demand.
-
On-demand Import (Recommended)
We recommend using
unplugin-vue-componentsfor auto-importing components.
// vite.config.ts
import Components from 'unplugin-vue-components/vite';
import UiResolver from '@soybeanjs/ui/resolver';
export default defineConfig({
plugins: [
Components({
resolvers: [UiResolver()]
})
]
});- Nuxt Module
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@soybeanjs/ui/nuxt']
});The headless components provide the functionality without the styles.
For data-driven multi-slot patterns, prefer the exported Compact variant when it exists. It is the headless entry point for opinionated composition, while the regular parts remain available for fully manual assembly.
<script setup>
import { AccordionRoot, AccordionItem, AccordionTrigger, AccordionContent } from '@soybeanjs/headless';
</script>
<template>
<AccordionRoot>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
</AccordionItem>
</AccordionRoot>
</template>- Accessible: Follows WAI-ARIA patterns for roles, focus management, and keyboard navigation.
- Headless-first: Logic and styles are fully separated — use
@soybeanjs/headlessalone to build any design system. - Type Safe: Written in strict TypeScript. All props, emits, slots, and context values are typed.
- Customizable at every level: Override individual slot classes via the
uiprop, or swap the entire style layer. - Lightweight & Tree-shakable: Import only the components you use. Each component is individually tree-shakable.
- Nuxt ready: First-class Nuxt module with auto-registration (
@soybeanjs/ui/nuxt). - unplugin support: Auto-import resolver for
unplugin-vue-components(@soybeanjs/ui/resolver).