
后端前端【免费下载链接】react-starter-kitModern React starter kit with Bun, TypeScript, Tailwind CSS, tRPC, Stripe, and Cloudflare Workers. Production-ready monorepo for building fast web apps.项目地址https://gitcode.com/gh_mirrors/rea/react-starter-kit点击查看免费下载导读本文聚焦 React Starter Kit 的 UI 与主题层如何在 Monorepo 中组织 shadcn/uinew-york 风格组件、用bun ui:*命令维护组件库、基于 OKLCH CSS 变量与 Tailwind CSS v4 实现亮/暗双主题并避免常见的导入、样式丢失与首屏白闪问题。读完你将掌握packages/ui与apps/app之间的组件归属原则、cn()的类名合并机制、Jotai 驱动的主题状态流以及一套可直接复制的主题定制与故障排查方案。技术底座shadcn/ui Tailwind CSS v4整个 UI 层建立在两条主线上shadcn/uinew-york 风格组件不是以依赖包形式安装而是通过 CLI 将源码直接写入仓库由你完全掌控Tailwind CSS v4以 CSS-first 方式配置通过import tailwindcss与source声明扫描路径取代了旧版的tailwind.config.js。组件配置由 packages/ui/components.json 描述style: new-york、cssVariables: true、iconLibrary: lucide、别名/components、/lib/utils等是 shadcn CLI 生成代码时的依据。底层依赖包括 radix-ui。组件的两个家归属原则决定架构边界维度packages/ui/components/apps/app/components/内容shadcn/ui 原语——Button、Card等产品部件——AuthForm、UserMenu、NotFound等感知范围React、Radix、样式工具路由、查询、会话、产品业务规则维护方shadcn CLIbun ui:add你手写导入方式repo/ui/components/...判据只有一条一个组件只有在换个应用依然成立时才放进packages/ui。任何会伸手去拿路由、查询或会话的组件都属于 apps/app/components例如 auth-form.tsx、user-menu.tsx、sidebar-nav.tsx。这样packages/ui保持纯净、可复用而产品逻辑被隔离在应用层不会污染共享库。用 CLI 添加组件命令、产物与两个坑仓库根 package.json 提供了五个脚本实际实现在 packages/ui/scriptsbun ui:add button # 添加单个组件 bun ui:add dialog card select # 一次添加多个 bun ui:add --all # 添加 registry 中所有组件 bun ui:essentials # 添加精选入门组件集 bun ui:list # 列出已安装组件 bun ui:update # 从 registry 重新拉取已安装组件以 add.ts 为例其内部执行bunx shadcnlatest add 组件 --yes随后调用formatGeneratedFiles()用 Prettier 格式化生成的文件。从 package.json 的 scripts 可以看到add/list/update/essentials分别对应 scripts/add.ts、list.ts、update.ts、essentials.ts。坑一CLI 不会更新 barrel 导出添加组件只写文件并格式化不会触碰 packages/ui/index.ts 的导出。需要手动补一行// packages/ui/index.ts export * from ./components/toggle-group;否则import { ToggleGroup } from repo/ui无法解析。当前该文件已导出 avatar、button、card、checkbox、dialog、input、label、radio-group、scroll-area、select、separator、skeleton、switch、textarea、toggle、toggle-group 以及cn。坑二bun ui:update会原地覆盖本地修改CLI 生成物并不统一部分组件仍输出Context.Provider与useContext而本项目 ESLint 配置要求使用 React 19 的新形式Context与use()。因此更新前务必git diff审查对本地改动做好取舍。包结构速览packages/ui/ ├── components/ # 每组件一个文件 │ ├── button.tsx │ ├── card.tsx │ └── ... ├── hooks/ ├── lib/ │ └── utils.ts # cn() 工具 ├── scripts/ # CLI 工具add、list、update、essentials ├── components.json # shadcn CLI 配置——风格、别名、图标库 ├── styles.css # 仅服务于 shadcn CLI真实样式在应用中 ├── index.ts # barrel 导出 └── package.json两个关键细节/lib/utils别名的消费端解析机制CLI 生成的组件内部写import { cn } from /lib/utils。该别名并不由packages/ui自己的 tsconfig 解析而是由消费应用的配置解析——所以每个应用都必须保留一个再导出垫片如 apps/app/lib/utils.ts 的export { cn } from repo/ui。同理包内组件互相引用必须用相对路径./toggle若照抄 CLI 的/components/...形式会解析到应用里去导致构建失败。单一入口组件与cn从包根统一再导出应用只需从一个地方导入import { Button, Card, CardHeader, CardTitle, Input, cn } from repo/ui;packages/ui/package.json 的exports字段还开放了子路径./lib/utils、./components/*、./hooks/*、./lib/*peerDependencies 要求 React 19.2.4。使用组件以 Card 组合为例shadcn/ui 的复合组件遵循拼接而非继承模式。以Card为例其拆分为Card、CardHeader、CardTitle、CardDescription、CardContent等零件直接组合即可import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from repo/ui; import type { ReactNode } from react; type FeatureCardProps { title: string; description: string; children: ReactNode; }; export function FeatureCard({ title, description, children, }: FeatureCardProps) { return ( Card CardHeader CardTitle{title}/CardTitle CardDescription{description}/CardDescription /CardHeader CardContent{children}/CardContent /Card ); }cn()条件类名与冲突消解cn()定义在 packages/ui/lib/utils.ts本质是clsx与tailwind-merge的组合import { type ClassValue, clsx } from clsx; import { twMerge } from tailwind-merge; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }clsx负责条件拼接falsy 值自动忽略tailwind-merge负责冲突消解——后写的类胜出所以cn(p-4, p-6)得到p-6。这带来一个关键能力外部传入的className可以覆盖组件自身的默认样式而不必与默认类在 specificity 上搏斗import { Button, cn } from repo/ui; export function SaveButton({ isActive }: { isActive: boolean }) { return ( Button className{cn( transition-colors, isActive bg-primary text-primary-foreground, )} Save /Button ); }主题体系OKLCH 变量 theme inline映射颜色即变量改一处全局跟随主题颜色以 CSS 自定义属性定义在 apps/app/styles/globals.css使用OKLCH颜色空间对亮度的感知更均匀插值更一致。每个应用各持一套apps/app主应用一份营销站apps/web保持一份镜像副本。核心变量摘录:root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --primary: oklch(0.205 0 0); --primary-foreground: oklch(0.985 0 0); --destructive: oklch(0.577 0.245 27.325); /* ... */ } .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); /* ... */ }随后 apps/app/tailwind.config.css 用theme inline将这些变量映射成 Tailwind 工具类对应的主题令牌theme inline { --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) 4px); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); /* ... */ }于是bg-primary、text-muted-foreground等工具类自动解析到变量。要换肤就改变量——所有引用它的工具类随之生效。两个实战注意点令牌成对存在--primary是表面色--primary-foreground是绘于其上的文字色。改动一个必须检查另一个与之的可读性对比度亮暗都要改:root与.dark两处需同步编辑——在纯白背景上可读的颜色在近黑背景上往往不行。暗色模式dark类变体暗色模式通过自定义 Tailwind 变体键控在dark类上/* apps/app/tailwind.config.css */ custom-variant dark (:is(.dark *));apps/app下还额外import tw-animate-css为 dialog、select 等提供进入/退出动画工具类——缺少它这些类不会生成任何 CSS开合动画会直接消失见 tailwind.config.css 的注释。主题状态机Jotai atom而非 React Contextdark类由 apps/app/lib/theme.tsx 管理。整个设计有一个清晰的模型用户持有preferencelight/dark/system应用渲染theme只可能是light或darktheme 永远是派生的而不是一份需要手工同步的状态。import { useTheme } from /lib/theme; function ThemeButton() { const { theme, preference, setPreference } useTheme(); return ( button onClick{() setPreference(theme dark ? light : dark)} {preference system ? System (${theme}) : preference} /button ); }源码中有几个值得细读的实现点三个 atom 的分工themePreferenceAtomatomWithStorage持久化偏好storage key 为theme、systemThemeAtomatomWithLazy惰性读取prefers-color-scheme并在订阅期间持续监听系统变化、themeAtom纯派生preference system时取系统主题否则取偏好。派生关系的完整逻辑见 theme.tsx。健壮性处理themePreferenceAtom对getItem/setItem/subscribe都做了 try/catch 与值校验toPreference因为 localStorage 里什么脏数据都可能存在且getOnInit: true会让读取在模块求值期就发生——若浏览器拒绝存储访问未防护的读取会直接拖垮整个 bundle。atom 是私有实现细节对外只暴露useTheme()其返回类型被显式写出避免调用方触达 Jotai 的 setter 更新函数与RESET等持久化机制。ThemeSync组件theme.tsx挂载在 apps/app/index.tsx 的StoreProvider内、应用根部用useLayoutEffect把解析后的主题镜像到html切换dark类、设置color-scheme属性让滚动条、原生表单控件跟随、并把--theme-color变量的值写进meta nametheme-color。而StoreProviderlib/store.ts为全应用所有 Jotai atom 提供统一的 store 根。首屏防白闪index.html内联脚本纯 React 方案的问题在于主题在 bundle 加载完成后才落地暗色用户会看到一帧白屏。解法是 apps/app/index.html 中一段内联脚本在首次绘制前就把三件事定下来script (() { let preference; try { preference JSON.parse(localStorage.getItem(theme)); } catch { // Storage unavailable or corrupt – fall back to the OS setting. } const dark preference dark || (preference ! light matchMedia((prefers-color-scheme: dark)).matches); document.documentElement.classList.toggle(dark, dark); document.documentElement.style.colorScheme dark ? dark : light; if (dark) { document .querySelector(meta[nametheme-color]) .setAttribute(content, #0f0f0f); } })(); /script这段脚本有意重复了theme.tsx的部分解析逻辑且必须满足三条一致性约束storage key 与 JSON 编码必须与theme.tsx的atomWithStorage完全一致否则偏好读不到两处 hex 色值必须与 globals.css 中的--theme-color保持一致亮色#fafafa、暗色#0f0f0f——因为此刻样式表尚未解析脚本无法读取 CSS 变量只能硬编码注释里明确说明这是刻意为之site.manifest 中的theme_color: #fafafa也必须同步它只声明亮色加载先于任何样式表。ThemeSync在 React 挂载后接管并补齐内联脚本执行后、React 渲染前这段时间内发生的系统或跨标签页主题变化。切换器直接用ToggleGroup主题切换 UI 直接复用单选的 toggle-group.tsx单选的 ToggleGroup 天然携带 radio 语义与方向键导航无需再手写键盘处理。Tailwind 内容扫描source与完整类名约束Tailwind v4 通过扫描source列出的文件来发现类名。主应用 tailwind.config.css 的扫描清单import tailwindcss; source ./lib/**/*.{js,ts,jsx,tsx}; source ./routes/**/*.{js,ts,jsx,tsx}; source ./components/**/*.{js,ts,jsx,tsx}; source ./index.html; source ./index.tsx; source ../../packages/ui/components/**/*.{ts,tsx}; source ../../packages/ui/lib/**/*.{ts,tsx}; source ../../packages/ui/hooks/**/*.{ts,tsx};注意应用与共享包都必须列入否则只出现在packages/ui里的类会被生产构建剔除。扫描是文本级的Tailwind 只认完整类名字符串bg-red-500能被发现而bg-${color}-500这种字符串插值拼出来的类名不会被识别——请改为维护完整的类名映射表。故障排查速查表repo/ui导入失败组件可能未安装或未导出。先bun ui:list确认再检查 packages/ui/index.ts 是否已添加对应导出行——bun ui:add不会替你加。开发环境正常、构建产物丢样式类名所在文件不在任何source覆盖范围内或者类名由字符串插值拼装而成。参见上文Tailwind 内容扫描一节。完全没有任何样式检查 apps/app/index.tsx 是否导入了./styles/globals.css——这是样式管线的入口缺失即全裸。TypeScript 无法解析repo/ui消费应用需要同时具备路径别名与项目引用二者缺一不可{ compilerOptions: { paths: { repo/ui: [../../packages/ui] } }, references: [{ path: ../../packages/ui }] }延伸阅读packages/ui/README.md共享 UI 包的使用说明与组件清单apps/app/components产品级组件AuthForm、UserMenu、布局侧边栏等的实际写法docs/frontend 与 docs/recipes/new-page.md前端结构、表单与新增页面的配套指南packages/ui/styles.css 与 components.jsonshadcn CLI 的配置与生成基础apps/web/styles/globals.css营销站的主题变量镜像副本赞分享后端前端【免费下载链接】react-starter-kitModern React starter kit with Bun, TypeScript, Tailwind CSS, tRPC, Stripe, and Cloudflare Workers. Production-ready monorepo for building fast web apps.项目地址https://gitcode.com/gh_mirrors/rea/react-starter-kit点击查看免费下载相关推荐Pixi3D插件开发如何扩展Pixi3D功能以满足特定需求Pixi3D插件开发如何扩展Pixi3D功能以满足特定需求 Pixi3D是PixiJS的3D渲染器它能够与2D应用程序无缝集成为开发者提供了在2D环境中实后端前端Cherry Studio UI 组件库迁移实战指南从 antd styled-components 到 shadcn/ui Tailwind CSS v4Cherry Studio UI 组件库迁移实战指南从 antd styled components 到 shadcn/ui Tailwind CSS人工智能大模型AI 应用交互助手本地部署React Starter Kit终极UI组件库shadcn/ui集成与自定义组件开发完整指南React Starter Kit终极UI组件库shadcn/ui集成与自定义组件开发完整指南 React Starter Kit是一个现代化的单页Web应用后端前端上一篇工业级音频智能新突破Step-Audio 2多模态模型重塑语音交互体验下一篇Android视频水印与叠加BitmapOverlayVideoProcessor图像合成技术完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考