This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called vp. Vite+ is distinct from Vite, and it invokes Vite through vp dev and vp build. Run vp help to print a list of commands and vp <command> --help for information about a specific command.
Docs are local at node_modules/vite-plus/docs or online at https://viteplus.dev/guide/.
- Run
vp installafter pulling remote changes and before getting started. - Run
vp checkandvp testto format, lint, type check and test changes. - Check if there are
vite.config.tstasks orpackage.jsonscripts necessary for validation, run viavp run <script>. - If setup, runtime, or package-manager behavior looks wrong, run
vp env doctorand include its output when asking for help.
solid-element-ui is a SolidJS component library built as a thin wrapper around Kobalte (headless UI primitives for SolidJS). It adds Tailwind CSS v4 styling via tailwind-variants (tv). The library is published as the npm package solid-element-ui.
| Path | Purpose |
|---|---|
packages/ui/ |
📦 The component library source (solid-element-ui npm package) |
apps/docs/ |
📖 Documentation site built with SolidStart + MDX |
tools/ |
(Reserved for future tooling) |
vp install # Install all dependencies (uses bun)
vp check # Lint + format + type check (oxlint + oxfmt + tsc)
vp test # Run tests (vitest via Vite+)
vp run -r build # Build all workspaces
vp run dev # Start docs dev server (vp run docs#dev)
vp run ready # Full CI check: check → test → build- Package manager:
bunv1.3.14 (specified indevEngines.packageManager) - Node:
>=22.18.0 - TypeScript:
^5.7withverbatimModuleSyntax,erasableSyntaxOnly,strict: true
Every component in packages/ui/src/<name>/<name>.tsx follows this pattern:
Each component wraps a Kobalte primitive (imported with K prefix) and adds Tailwind styling:
import { Button as KButton } from "@kobalte/core/button";
import { splitProps, type ComponentProps } from "solid-js";
import { tv, type VariantProps } from "tailwind-variants";Always use tv() with { twMerge: true } option. Two patterns:
- Single slot:
tv({ base: "...", variants: {...} })— for simple components (Badge, Skeleton, Link) - Multi-slot:
tv({ slots: { root, trigger, content, ... }, variants: {...} })— for complex components (Button, Dialog, Select, Switch)
Almost all components split props into three groups:
- local: Component-consumed props (
class,children, custom props likeloading,leftIcon) - variantKeys: tv variant keys (
variant,size,color,orientation) - others: Remaining props forwarded to the Kobalte root component
const [local, variantKeys, others] = splitProps(
props,
["class", "children", "loading"], // local
["variant", "size", "color"], // variant keys
);Simple components without tv variants may use two-way split (local + others).
- Each component folder has a single
<name>.tsxfile (noindex.tsx) - All exports are re-exported from
packages/ui/src/index.tsx ComponentProps<typeof KComponent>is used for type intersection
For components with multiple sub-components (like <Meter.Label>, <Meter.Track>), use Object.assign:
export const Meter = Object.assign(Root, { Label, Track, Fill, ValueText });Uses @tailwindcss/vite plugin (NOT PostCSS). Key differences from v3:
@import "tailwindcss"instead of@tailwinddirectives@themeblock for design tokens@custom-variantfor custom variants@sourceto scan additional paths for class detection
All components use CSS variable-based semantic colors (defined in packages/ui/src/style/global.css):
| Token | Usage |
|---|---|
primary / success / warning / danger |
Semantic colors (blue/green/orange/red) |
main / muted / reversal |
Text colors |
app / foreground / reversal-bg |
Background colors |
base / light / ring |
Border/ring colors |
zinc-950 or dark:). Some older components still use hardcoded values — prefer the token system for new code.
Uses custom data-theme attribute (NOT class or prefers-color-scheme):
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *))- Switch themes via
document.documentElement.setAttribute("data-theme", val) - Supported themes:
light(default),dark,coffee - CSS variables switch in
[data-theme="dark"]/[data-theme="coffee"]blocks
The library's global.css is imported in src/index.tsx. Consumers can also import solid-element-ui/index.css.
Defined in global.css: accordion-down, fade-in, fade-out, slide-in, collapsible-down/up, swipe-out. Use via animate-* utility classes.
- Framework: SolidStart (
@solidjs/startv1.3.2) powered by Vinxi - Routing: File-based routing via
@solidjs/start/router(FileRoutes) - Content: MDX files in
apps/docs/src/routes/with@vinxi/plugin-mdx+solid-mdx - Layout:
app.tsx→ Nav (top bar with theme selector) + Aside (sidebar component nav) + main content - Styling: Uses
@tailwindcss/typography— wrap demos innot-proseclass to escape prose styles - API tables: Manually written Markdown tables in MDX files
- 404 route:
[...404].mdx - Route filter:
getFilteredRoutes.tsfilters out 404, color-*, rating-group routes
| Package | Purpose |
|---|---|
solid-js ^1.9.13 |
SolidJS core (peer dependency) |
@kobalte/core ^0.13.11 |
Headless UI primitives (accessibility + behavior) |
tailwind-variants ^3.2.2 |
Type-safe component variant definitions |
tailwind-merge ^3.6.0 |
Smart Tailwind class merging (used by tv) |
lucide-solid ^1.16.0 |
Icon library |
vinxi ^0.5.11 |
Meta-framework engine for docs site |
- Dark mode inconsistency: Some components use hardcoded
zinccolors ordark:prefix instead of semantic tokens — prefer the CSS variable system for new components. verbatimModuleSyntax: true: Always useimport typefor type-only imports. Never useenum(disallowed byerasableSyntaxOnly).- Global CSS imported at package root:
import "./style/global.css"inindex.tsxmeans styles are bundled. For consumers, usesolid-element-ui/index.css. - AlertDialog uses internal state: It manages
isOpeninternally viacreateSignaland does NOT acceptopen/onOpenChangefrom parents. - No SSR guards: Most components don't check
isServer— only Toast does. Be mindful of SSR if adding new browser-API-dependent code. - Children as render prop:
ToggleButtonaccepts children as a function{(state) => ...}pattern — check component's type definition. - Chinese text hardcoded: Some components have Chinese UI strings (Dialog "关闭", AlertDialog "取消"/"确认") — these are intentional.
- Components without
classforwarding: Always splitclassinto local props and pass to tv styles fortailwind-mergeto work properly.
packages/ui/vite.config.ts: Builds library as ESM, externalizessolid-js,@kobalte/core,tailwind-variants,tailwind-mergepackages/ui/tsconfig.json:jsx: preserve,jsxImportSource: solid-js,moduleResolution: bundler- Type declarations generated by
vite-plugin-dts - Root
vite.config.tsconfigures oxlint rules for Vite+