OpenMontage 技能库实战:基于 Tailwind CSS v4 构建可扩展设计系统
发布时间:2026/9/11 12:17:18来源:尧图网络
OpenMontage 技能库实战基于 Tailwind CSS v4 构建可扩展设计系统【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage本指南以 OpenMontage 仓库中 tailwind-design-system 技能文档 为骨架系统讲解 Tailwind CSS v4 的 CSS-first 配置范式从theme设计令牌、custom-variant暗黑模式到 CVA 组件变体、React 19 复合组件、原生动画与 v3→v4 迁移清单。读完本文你将掌握用 v4 的纯 CSS 方式构建一整套生产级、可访问、响应式的组件库并能对照仓库内 HyperFrames Tailwind 参考 在固定视口渲染场景下规避 v4 的坑点。何时使用这套技能在 OpenMontage 的 Agent 技能体系中tailwind-design-system属于 skills/INDEX.md 中Design类别下的核心设计技能。按照技能文档的 frontmatter 定义以下场景应主动调用它使用 Tailwind v4 创建组件库以 CSS-first 配置实现设计令牌design tokens与主题化构建响应式、可访问的组件在代码库中统一 UI 模式从 Tailwind v3 迁移到 v4使用原生 CSS 特性搭建暗黑模式。注意该技能面向 Tailwind CSS v42024。对于 v3 项目应优先参考官方升级指南再回到本技能落地。v3 到 v4 的关键变化速查表技能文档用一张对照表总结了 v4 相对 v3 的范式转移这是理解全文的基础v3 模式v4 模式tailwind.config.tstheme写在 CSS 中tailwind base/components/utilitiesimport tailwindcssdarkMode: classcustom-variant dark (:where(.dark, .dark *))theme.extend.colorstheme { --color-*: value }require(tailwindcss-animate)CSSkeyframes放进theme 用starting-style做入场动画一句话概括配置从 JS 配置文件搬进了 CSS一切以原生 CSS 变量为中心。CSS-first 配置从零搭建完整主题v4 的核心是“配置即 CSS”。以下是一份完整的app.css示例它同时定义了语义色板使用 OKLCH 色彩空间、圆角令牌、动画令牌与暗黑模式覆盖/* app.css - Tailwind v4 CSS-first configuration */ import tailwindcss; /* Define your theme with theme */ theme { /* Semantic color tokens using OKLCH for better color perception */ --color-background: oklch(100% 0 0); --color-foreground: oklch(14.5% 0.025 264); --color-primary: oklch(14.5% 0.025 264); --color-primary-foreground: oklch(98% 0.01 264); --color-secondary: oklch(96% 0.01 264); --color-secondary-foreground: oklch(14.5% 0.025 264); --color-muted: oklch(96% 0.01 264); --color-muted-foreground: oklch(46% 0.02 264); --color-accent: oklch(96% 0.01 264); --color-accent-foreground: oklch(14.5% 0.025 264); --color-destructive: oklch(53% 0.22 27); --color-destructive-foreground: oklch(98% 0.01 264); --color-border: oklch(91% 0.01 264); --color-ring: oklch(14.5% 0.025 264); --color-card: oklch(100% 0 0); --color-card-foreground: oklch(14.5% 0.025 264); /* Ring offset for focus states */ --color-ring-offset: oklch(100% 0 0); /* Radius tokens */ --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; /* Animation tokens - keyframes inside theme are output when referenced by --animate-* variables */ --animate-fade-in: fade-in 0.2s ease-out; --animate-fade-out: fade-out 0.2s ease-in; --animate-slide-in: slide-in 0.3s ease-out; --animate-slide-out: slide-out 0.3s ease-in; keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } keyframes slide-in { from { transform: translateY(-0.5rem); opacity: 0; } to { transform: translateY(0); opacity: 1; } } keyframes slide-out { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-0.5rem); opacity: 0; } } } /* Dark mode variant - use custom-variant for class-based dark mode */ custom-variant dark (:where(.dark, .dark *)); /* Dark mode theme overrides */ .dark { --color-background: oklch(14.5% 0.025 264); --color-foreground: oklch(98% 0.01 264); --color-primary: oklch(98% 0.01 264); --color-primary-foreground: oklch(14.5% 0.025 264); --color-secondary: oklch(22% 0.02 264); --color-secondary-foreground: oklch(98% 0.01 264); --color-muted: oklch(22% 0.02 264); --color-muted-foreground: oklch(65% 0.02 264); --color-accent: oklch(22% 0.02 264); --color-accent-foreground: oklch(98% 0.01 264); --color-destructive: oklch(42% 0.15 27); --color-destructive-foreground: oklch(98% 0.01 264); --color-border: oklch(22% 0.02 264); --color-ring: oklch(83% 0.02 264); --color-card: oklch(14.5% 0.025 264); --color-card-foreground: oklch(98% 0.01 264); --color-ring-offset: oklch(14.5% 0.025 264); } /* Base styles */ layer base { * { apply border-border; } body { apply bg-background text-foreground antialiased; } }关键点解读import tailwindcss替代三条tailwind指令v4 默认输出 base、components、utilities 三层无需再手动拆分。theme即配置在theme中声明的--color-*、--radius-*、--animate-*变量会自动生成对应的工具类如--color-primary→bg-primary、text-primary、border-primary。keyframes放入theme只有被--animate-*变量引用时才会随主题输出避免产生无用的关键帧代码。custom-variant dark恢复 class 暗黑模式选择器:where(.dark, .dark *)会同时命中.dark元素及其所有后代实现整棵子树切换。OKLCH 语义色板技能文档推荐优先使用 OKLCH 而非 HSL因为其感知均匀性更好前景/背景、主/辅色均围绕同一个色调264搭配保证对比度协调。设计令牌的三级层次技能文档给出了清晰的令牌分层模型这是“可扩展设计系统”的灵魂Brand Tokens (abstract) └── Semantic Tokens (purpose) └── Component Tokens (specific) Example: oklch(45% 0.2 260) → --color-primary → bg-primaryBrand Tokens品牌令牌最底层抽象如裸色值oklch(45% 0.2 260)不绑定任何语义Semantic Tokens语义令牌把品牌色映射到用途如--color-primaryComponent Tokens组件令牌语义令牌落到具体组件用法如bg-primary。由此带来的硬性规范是组件里只写bg-primary绝不写bg-blue-500。这样当品牌色调整时只需改动theme一处。组件架构与 CVA 变体模式组件设计遵循“基础样式 → 变体 → 尺寸 → 状态 → 覆盖”的分层架构Base styles → Variants → Sizes → States → OverridesPattern 1CVAClass Variance Authority组件用class-variance-authority实现类型安全的变体组合是 v4 组件库的标配写法// components/ui/button.tsx import { Slot } from radix-ui/react-slot import { cva, type VariantProps } from class-variance-authority import { cn } from /lib/utils const buttonVariants cva( // Base styles - v4 uses native CSS variables inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50, { variants: { variant: { default: bg-primary text-primary-foreground hover:bg-primary/90, destructive: bg-destructive text-destructive-foreground hover:bg-destructive/90, outline: border border-border bg-background hover:bg-accent hover:text-accent-foreground, secondary: bg-secondary text-secondary-foreground hover:bg-secondary/80, ghost: hover:bg-accent hover:text-accent-foreground, link: text-primary underline-offset-4 hover:underline, }, size: { default: h-10 px-4 py-2, sm: h-9 rounded-md px-3, lg: h-11 rounded-md px-8, icon: size-10, }, }, defaultVariants: { variant: default, size: default, }, } ) export interface ButtonProps extends React.ButtonHTMLAttributesHTMLButtonElement, VariantPropstypeof buttonVariants { asChild?: boolean } // React 19: No forwardRef needed export function Button({ className, variant, size, asChild false, ref, ...props }: ButtonProps { ref?: React.RefHTMLButtonElement }) { const Comp asChild ? Slot : button return ( Comp className{cn(buttonVariants({ variant, size, className }))} ref{ref} {...props} / ) } // Usage Button variantdestructive sizelgDelete/Button Button variantoutlineCancel/Button Button asChildLink href/homeHome/Link/Button要点基础样式集中描述“排版、圆角、聚焦环、禁用态”变体只叠加差异/90、/80这类透明度修饰符在 v4 中基于color-mix()实现size-10是 v4 新增的宽高简写asChild配合 Radix 的Slot让按钮可以渲染为Link等任意元素。Pattern 2React 19 复合组件React 19 中ref成为普通 prop不再需要forwardRef。技能文档以 Card 系列组件演示复合组件写法// components/ui/card.tsx import { cn } from /lib/utils // React 19: ref is a regular prop, no forwardRef export function Card({ className, ref, ...props }: React.HTMLAttributesHTMLDivElement { ref?: React.RefHTMLDivElement }) { return ( div ref{ref} className{cn( rounded-lg border border-border bg-card text-card-foreground shadow-sm, className )} {...props} / ) } export function CardHeader({ className, ref, ...props }: React.HTMLAttributesHTMLDivElement { ref?: React.RefHTMLDivElement }) { return ( div ref{ref} className{cn(flex flex-col space-y-1.5 p-6, className)} {...props} / ) } export function CardTitle({ className, ref, ...props }: React.HTMLAttributesHTMLHeadingElement { ref?: React.RefHTMLHeadingElement }) { return ( h3 ref{ref} className{cn(text-2xl font-semibold leading-none tracking-tight, className)} {...props} / ) } export function CardDescription({ className, ref, ...props }: React.HTMLAttributesHTMLParagraphElement { ref?: React.RefHTMLParagraphElement }) { return ( p ref{ref} className{cn(text-sm text-muted-foreground, className)} {...props} / ) } export function CardContent({ className, ref, ...props }: React.HTMLAttributesHTMLDivElement { ref?: React.RefHTMLDivElement }) { return ( div ref{ref} className{cn(p-6 pt-0, className)} {...props} / ) } export function CardFooter({ className, ref, ...props }: React.HTMLAttributesHTMLDivElement { ref?: React.RefHTMLDivElement }) { return ( div ref{ref} className{cn(flex items-center p-6 pt-0, className)} {...props} / ) } // Usage Card CardHeader CardTitleAccount/CardTitle CardDescriptionManage your account settings/CardDescription /CardHeader CardContent form.../form /CardContent CardFooter ButtonSave/Button /CardFooter /Card复合组件把容器、头部、标题、描述、内容、底部按语义拆分成独立导出的子组件每个子组件通过cn()合并外部传入的className实现“封闭内部样式、开放覆盖入口”的扩展模型。Pattern 3表单组件与校验集成表单输入框需要同时处理焦点环、错误态与无障碍标注。技能文档展示了InputLabel与 React Hook Form Zod 的完整组合// components/ui/input.tsx import { cn } from /lib/utils export interface InputProps extends React.InputHTMLAttributesHTMLInputElement { error?: string ref?: React.RefHTMLInputElement } export function Input({ className, type, error, ref, ...props }: InputProps) { return ( div classNamerelative input type{type} className{cn( flex h-10 w-full rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50, error border-destructive focus-visible:ring-destructive, className )} ref{ref} aria-invalid{!!error} aria-describedby{error ? ${props.id}-error : undefined} {...props} / {error ( p id{${props.id}-error} classNamemt-1 text-sm text-destructive rolealert {error} /p )} /div ) } // components/ui/label.tsx import { cva, type VariantProps } from class-variance-authority const labelVariants cva( text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ) export function Label({ className, ref, ...props }: React.LabelHTMLAttributesHTMLLabelElement { ref?: React.RefHTMLLabelElement }) { return ( label ref{ref} className{cn(labelVariants(), className)} {...props} / ) } // Usage with React Hook Form Zod import { useForm } from react-hook-form import { zodResolver } from hookform/resolvers/zod import { z } from zod const schema z.object({ email: z.string().email(Invalid email address), password: z.string().min(8, Password must be at least 8 characters), }) function LoginForm() { const { register, handleSubmit, formState: { errors } } useForm({ resolver: zodResolver(schema), }) return ( form onSubmit{handleSubmit(onSubmit)} classNamespace-y-4 div classNamespace-y-2 Label htmlForemailEmail/Label Input idemail typeemail {...register(email)} error{errors.email?.message} / /div div classNamespace-y-2 Label htmlForpasswordPassword/Label Input idpassword typepassword {...register(password)} error{errors.password?.message} / /div Button typesubmit classNamew-fullSign In/Button /form ) }无障碍要点错误态通过aria-invalid标记输入框通过aria-describedby把错误消息与输入框关联错误消息本身使用rolealert错误出现时边框与焦点环同步切换为destructive语义色。Pattern 4响应式网格与容器把响应式断点抽象成 CVA 变体可以让调用方用cols与gap两个 prop 完成全部布局控制// components/ui/grid.tsx import { cn } from /lib/utils import { cva, type VariantProps } from class-variance-authority const gridVariants cva(grid, { variants: { cols: { 1: grid-cols-1, 2: grid-cols-1 sm:grid-cols-2, 3: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3, 4: grid-cols-1 sm:grid-cols-2 lg:grid-cols-4, 5: grid-cols-2 sm:grid-cols-3 lg:grid-cols-5, 6: grid-cols-2 sm:grid-cols-3 lg:grid-cols-6, }, gap: { none: gap-0, sm: gap-2, md: gap-4, lg: gap-6, xl: gap-8, }, }, defaultVariants: { cols: 3, gap: md, }, }) interface GridProps extends React.HTMLAttributesHTMLDivElement, VariantPropstypeof gridVariants {} export function Grid({ className, cols, gap, ...props }: GridProps) { return ( div className{cn(gridVariants({ cols, gap, className }))} {...props} / ) } // Container component const containerVariants cva(mx-auto w-full px-4 sm:px-6 lg:px-8, { variants: { size: { sm: max-w-screen-sm, md: max-w-screen-md, lg: max-w-screen-lg, xl: max-w-screen-xl, 2xl: max-w-screen-2xl, full: max-w-full, }, }, defaultVariants: { size: xl, }, }) interface ContainerProps extends React.HTMLAttributesHTMLDivElement, VariantPropstypeof containerVariants {} export function Container({ className, size, ...props }: ContainerProps) { return ( div className{cn(containerVariants({ size, className }))} {...props} / ) } // Usage Container Grid cols{4} gaplg {products.map((product) ( ProductCard key{product.id} product{product} / ))} /Grid /Container注意网格变体的设计意图移动端始终 1 列起步sm断点升到 2 列lg断点再升到满列数容器负责控制最大宽度与水平内边距。Pattern 5v4 原生 CSS 动画v4 用starting-style与allow-discrete过渡实现了纯 CSS 的入场/出场动画可以完全替代tailwindcss-animate插件。技能文档以对话框为例先定义动画令牌/* In your CSS file - native starting-style for entry animations */ theme { --animate-dialog-in: dialog-fade-in 0.2s ease-out; --animate-dialog-out: dialog-fade-out 0.15s ease-in; } keyframes dialog-fade-in { from { opacity: 0; transform: scale(0.95) translateY(-0.5rem); } to { opacity: 1; transform: scale(1) translateY(0); } } keyframes dialog-fade-out { from { opacity: 1; transform: scale(1) translateY(0); } to { opacity: 0; transform: scale(0.95) translateY(-0.5rem); } } /* Native popover animations using starting-style */ [popover] { transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete; opacity: 0; transform: scale(0.95); } [popover]:popover-open { opacity: 1; transform: scale(1); } starting-style { [popover]:popover-open { opacity: 0; transform: scale(0.95); } }再结合 Radix Dialog 与data-[state...]属性选择器驱动动画// components/ui/dialog.tsx - Using native popover API import * as DialogPrimitive from radix-ui/react-dialog import { cn } from /lib/utils const DialogPortal DialogPrimitive.Portal export function DialogOverlay({ className, ref, ...props }: React.ComponentPropsWithoutReftypeof DialogPrimitive.Overlay { ref?: React.RefHTMLDivElement }) { return ( DialogPrimitive.Overlay ref{ref} className{cn( fixed inset-0 z-50 bg-black/80, data-[stateopen]:animate-fade-in>// providers/ThemeProvider.tsx - Simplified for v4 use client import { createContext, useContext, useEffect, useState } from react type Theme dark | light | system interface ThemeContextType { theme: Theme setTheme: (theme: Theme) void resolvedTheme: dark | light } const ThemeContext createContextThemeContextType | undefined(undefined) export function ThemeProvider({ children, defaultTheme system, storageKey theme, }: { children: React.ReactNode defaultTheme?: Theme storageKey?: string }) { const [theme, setTheme] useStateTheme(defaultTheme) const [resolvedTheme, setResolvedTheme] useStatedark | light(light) useEffect(() { const stored localStorage.getItem(storageKey) as Theme | null if (stored) setTheme(stored) }, [storageKey]) useEffect(() { const root document.documentElement root.classList.remove(light, dark) const resolved theme system ? (window.matchMedia((prefers-color-scheme: dark)).matches ? dark : light) : theme root.classList.add(resolved) setResolvedTheme(resolved) // Update meta theme-color for mobile browsers const metaThemeColor document.querySelector(meta[nametheme-color]) if (metaThemeColor) { metaThemeColor.setAttribute(content, resolved dark ? #09090b : #ffffff) } }, [theme]) return ( ThemeContext.Provider value{{ theme, setTheme: (newTheme) { localStorage.setItem(storageKey, newTheme) setTheme(newTheme) }, resolvedTheme, }} {children} /ThemeContext.Provider ) } export const useTheme () { const context useContext(ThemeContext) if (!context) throw new Error(useTheme must be used within ThemeProvider) return context } // components/ThemeToggle.tsx import { Moon, Sun } from lucide-react import { useTheme } from /providers/ThemeProvider export function ThemeToggle() { const { resolvedTheme, setTheme } useTheme() return ( Button variantghost sizeicon onClick{() setTheme(resolvedTheme dark ? light : dark)} Sun classNamesize-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0 / Moon classNameabsolute size-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100 / span classNamesr-onlyToggle theme/span /Button ) }实现要点system模式通过window.matchMedia((prefers-color-scheme: dark))解析resolvedTheme供需要感知实际明暗的代码使用切换图标用rotate/scale过渡实现太阳与月亮的交叉淡入淡出sr-only保证屏幕阅读器可读。工具函数cn / focusRing / disabled组件库的样式合并统一收敛到cn并把高频状态组合抽成常量// lib/utils.ts import { type ClassValue, clsx } from clsx; import { twMerge } from tailwind-merge; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } // Focus ring utility export const focusRing cn( focus-visible:outline-none focus-visible:ring-2, focus-visible:ring-ring focus-visible:ring-offset-2, ); // Disabled utility export const disabled disabled:pointer-events-none disabled:opacity-50;cn先用clsx处理条件类名再用tailwind-merge去重冲突的 Tailwind 类例如后传入的bg-red-500会正确覆盖前一个bg-blue-500这也是所有组件能安全接收外部className的前提。高级 v4 模式用utility定义自定义工具类utility替代 v3 的addUtilities插件机制直接在 CSS 中声明可被变体如hover:、dark:组合的自定义工具/* Custom utility for decorative lines */ utility line-t { apply relative before:absolute before:top-0 before:-left-[100vw] before:h-px before:w-[200vw] before:bg-gray-950/5 dark:before:bg-white/10; } /* Custom utility for text gradients */ utility text-gradient { apply bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent; }theme inline与theme static/* Use theme inline when referencing other CSS variables */ theme inline { --font-sans: var(--font-inter), system-ui; } /* Use theme static to always generate CSS variables (even when unused) */ theme static { --color-brand: oklch(65% 0.15 240); } /* Import with theme options */ import tailwindcss theme(static);inline适用于变量值引用其他 CSS 变量需在渲染时解析的场景static强制所有变量无条件输出适合需要对外暴露整套令牌如第三方消费的情况。命名空间覆盖需要从零定制色板时可先清空默认颜色再定义自己的令牌theme { /* Clear all default colors and define your own */ --color-*: initial; --color-white: #fff; --color-black: #000; --color-primary: oklch(45% 0.2 260); --color-secondary: oklch(65% 0.15 200); /* Clear ALL defaults for a minimal setup */ /* --*: initial; */ }--color-*: initial只清空颜色族--*: initial则清空全部默认令牌实现极简最小化主题。用color-mix()生成半透明色阶无需逐一手工调色直接基于主色按比例混合透明色生成 50/100/200 色阶theme { /* Use color-mix() for alpha variants */ --color-primary-50: color-mix(in oklab, var(--color-primary) 5%, transparent); --color-primary-100: color-mix( in oklab, var(--color-primary) 10%, transparent ); --color-primary-200: color-mix( in oklab, var(--color-primary) 20%, transparent ); }容器查询令牌v4 支持通过--container-*令牌自定义容器查询断点theme { --container-xs: 20rem; --container-sm: 24rem; --container-md: 28rem; --container-lg: 32rem; }v3 到 v4 迁移清单技能文档给出的迁移清单可逐项勾选执行用 CSS 的theme块替换tailwind.config.ts将tailwind base/components/utilities改为import tailwindcss把颜色定义迁移到theme { --color-*: value }用custom-variant dark替换darkMode: class把keyframes移入theme块保证关键帧随主题输出用原生 CSS 动画替换require(tailwindcss-animate)把h-10 w-10更新为size-10新简写工具类移除forwardRefReact 19 已将 ref 作为 prop 传递考虑改用 OKLCH 颜色以获得更好的感知一致性用utility指令替换自定义插件最佳实践Dos使用theme块—— CSS-first 配置是 v4 的核心范式使用 OKLCH 颜色—— 比 HSL 具有更好的感知均匀性用 CVA 组合变体—— 类型安全的变体定义使用语义令牌—— 写bg-primary而不是bg-blue-500使用size-*—— 新的w-* h-*简写补充无障碍—— ARIA 属性与焦点态不可省略。Donts不要使用tailwind.config.ts—— 改用 CSS 的theme不要使用tailwind指令—— 改用import tailwindcss不要使用forwardRef—— React 19 中 ref 直接作为 prop不要滥用任意值—— 优先扩展theme不要硬编码颜色—— 一律使用语义令牌不要忘记暗黑模式—— 两种主题都要测试。仓库落地v4 在固定视口渲染场景的护栏OpenMontage 仓库中的 HyperFrames Tailwind 参考 给出了 v4 在视频合成渲染场景下的硬性规则与本文的设计系统技能互为补充。核心约束如下版本契约HyperFramesinit --tailwind固定使用tailwindcss/browser4.2.4浏览器运行时保持确定性渲染不替换为 unpinned 的 CDNCSS-first 写法在style typetext/tailwindcss内使用theme与utility定义令牌与自定义工具禁止 v3 的tailwind base/components/utilities写法也不要仅为配色/字体新增tailwind.config.jsv3 迁移路径若从 v3 迁移需显式用config ./tailwind.config.js;加载旧配置v4 不会自动探测 v3 配置文件动态类名安全浏览器运行时只扫描它能看到的类名切勿在 seek 时动态拼装bg-${color}-500这类类名应把完整类令牌静态写进 HTML 或data-*变体渲染安全护栏固定视口下禁用md:/lg:断点关键动画交给 GSAP 等可 seek 的适配器不用transition-*hover:/focus:等交互变体在渲染期不会触发v4 中裸border默认为currentColorv3 是gray-200必须显式写颜色留意 v4 工具类改名shadow-sm→shadow-xs、rounded-sm→rounded-xs、outline-none→outline-hidden、flex-shrink-*→shrink-*、flex-grow-*→grow-*。这些规则与本文的 CVA 组件模式并不冲突组件库负责常规交互型 Web 界面而渲染场景应把布局与视觉交给静态类 theme令牌把时间轴关键帧交给 GSAP—— 这正是 OpenMontage 将tailwind-design-system与hyperframes-*系列技能组合使用时遵循的分工边界。小结从theme设计令牌到 CVA 变体组件从starting-style原生动画到utility自定义工具Tailwind v4 把整套设计系统能力收敛到了纯 CSS 层。本文完整覆盖了 tailwind-design-system 技能文档 的全部模式、迁移清单与最佳实践并结合 HyperFrames Tailwind 参考 给出了固定视口渲染场景下的落地护栏。按照迁移清单逐项改造即可让存量 v3 项目平滑进入 CSS-first 时代并在 Web 组件与视频合成两类场景中复用同一套语义令牌。【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网