mcpField
S
styling

styling

github.com/epicenterhq设计与 UI4.8k77 次安装2 周前更新
代码仓库

关于 styling

styling 是面向 Svelte 项目的界面样式技能,供开发者遵循仓库中的 Tailwind 与组件规范。它还说明了 flex 布局中可滚动区域的尺寸处理方式。

主要功能

  1. 01减少不必要的包装元素
  2. 02使用 Tailwind 工具类与语义令牌
  3. 03通过 tailwind-variants 管理组件变体
  4. 04用 HTML disabled 表达不可交互状态
  5. 05处理 flex 列布局中的滚动区域

适用场景

  1. 01统一 Svelte 界面的样式写法
  2. 02为共享组件选择合适的覆盖方式
  3. 03修复面板内容无法独立滚动的问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/epicenterhq/epicenter --skill styling

安装后在 Claude Code 中输入/styling确认已加载。

详细介绍

样式指南

如果样式依赖 shadcn-svelte 结构、类合并、变体、Bits UI 组合方式或本地封装的行为,请阅读 ui-design 的组件系统参考,了解上游依据。普通 Tailwind 工具类和下方仓库本地布局规则无需外部查询。

尽量减少封装元素

避免创建不必要的封装 div。如果直接将类应用到现有语义元素也能达到相同效果,应优先采用这种方式。

推荐(直接应用)
<main class="flex-1 mx-auto max-w-7xl">
	{@render children()}
</main>
避免(不必要的封装)
<main class="flex-1">
	<div class="mx-auto max-w-7xl">
		{@render children()}
	</div>
</main>

只要样式不与元素的语义用途冲突,也不会造成布局问题,这项原则就适用于所有元素。

Tailwind 最佳实践

  • 在应用中使用 @epicenter/ui/utils 的 cn(),在 UI 包中使用 ../utils.js,以便有条件地组合类
  • 局部布局和状态优先使用工具类,而非自定义 CSS
  • 优先使用共享比例和语义令牌工具类,而非任意方括号值和原始颜色
  • 使用 tailwind-variants 构建组件变体系统
  • 遵循颜色的 background/foreground 惯例
  • 使用 CSS 变量保持主题一致性

共享基础组件的覆盖样式

为本地 @epicenter/ui 基础组件添加样式时,请参考 ui-design 的组件系统文档。共享基础组件上的 Tailwind 类通常应表达父级布局或产品状态,而不是重新定义基础组件的视觉规格。

推荐的基础组件覆盖样式:

<Item.Button size="sm" class="w-full justify-start text-left" />

值得审视的基础组件覆盖样式:

<Item.Button size="sm" class="gap-2 rounded px-2 py-1.5 text-sm hover:bg-accent/50" />

如果覆盖样式重复设置尺寸、密度、圆角、间距、内边距、排版、悬停、聚焦或过渡类,请考虑基础组件是否需要尺寸、变体或语义封装。

禁用状态:使用 HTML disabled + Tailwind 变体

当交互元素可能无法交互时(空白分区、加载状态、没有条目),应使用 HTML disabled 属性,而不是 JS 条件保护。配合 Tailwind 的 enabled: 和 group-disabled: 变体使用。

为什么使用 disabled 而不是 JS 条件保护
  • disabled 会原生阻止点击:无需 if (!hasItems) return
  • 可使用 :disabled CSS 伪类设置样式
  • 符合无障碍语义(屏幕阅读器会播报“已变暗”或“不可用”)
  • Tailwind 的 enabled: 和 group-disabled: 变体可以顺畅组合
模式

<button
  class="group enabled:cursor-pointer enabled:hover:opacity-80"
  disabled={item.count === 0}
  onclick={toggle}
>
  {item.label} ({item.count})
  <ChevronIcon class="group-disabled:invisible" />
</button>
关键变体
  • enabled:cursor-pointer:仅在可点击时显示指针光标
  • enabled:hover:bg-accent/50:仅在可交互时应用悬停效果
  • group-disabled:invisible:父元素被禁用时隐藏子元素(例如展开箭头)
  • disabled:opacity-50:元素被禁用时降低其不透明度
反模式

<button
  class="cursor-pointer hover:opacity-80"
  onclick={() => { if (item.count > 0) toggle(); }}
>

JS 条件保护会让非交互元素上的 cursor-pointer 和 hover:opacity-80 继续生效。用户看到一个看起来可以点击、实际却没有反应的按钮。应使用 disabled,让浏览器和 CSS 来处理。

Flex 列滚动陷阱

当 flex 子元素使用 h-full(高度:100%),但与其他同处一个 flex 列中的兄弟元素(页眉、工具栏、页脚)共用布局时,它计算出的高度会等于父元素的完整高度:它会溢出并覆盖兄弟元素,而不是占据剩余空间。内容会被裁切,或将布局挤出视口,内部滚动区域也永远不会启用。

这是本代码库中最常见的布局错误。以下情况都可能出现:

  • 需要滚动的 Resizable.Pane(paneforge)内有组件
  • flex 列中有一个 ScrollArea.Root(bits-ui)或 overflow-auto div,且旁边有页眉或工具栏
  • 任何需要让某个分区独立滚动的分屏或面板布局
修复方法:flex-1 min-h-0 overflow-hidden

将包含可滚动内容的 flex 子元素上的 h-full 替换为这三个工具类。它们分别解决不同的问题:

工具类作用必要原因
flex-1占据兄弟元素之后的剩余空间h-full = 父元素高度的 100%,不考虑兄弟元素。flex-1 = 剩余空间。
min-h-0允许元素缩小到内容高度以下Flex 项默认 min-height: auto,因此不能小于其内容的高度。
overflow-hidden建立有界高度上下文没有它,带有 overflow-auto 或 ScrollArea 的子元素就没有可用于滚动的高度上限。

这三个都不可或缺。缺少其中任何一个,修复都会失效:

  • 缺少 flex-1:元素仍为父元素高度的 100%,会溢出并覆盖兄弟元素
  • 缺少 min-h-0:元素拒绝缩小,内容会把它撑得更高
  • 缺少 overflow-hidden:内部滚动容器没有有界祖先,因此会扩展而不是滚动
修改前 / 修改后

<main class="flex h-full flex-col overflow-hidden">
  <div class="border-b px-4 py-2">Toolbar</div>
  <MyScrollableContent class="h-full" />  
</main>


<main class="flex h-full flex-col overflow-hidden">
  <div class="border-b px-4 py-2">Toolbar</div>
  <MyScrollableContent class="flex-1 min-h-0 overflow-hidden" />
</main>
在可调整大小的窗格中(paneforge)

Paneforge 的 Pane 组件通过 flex 比例设置宽度,但不会限制高度或裁剪溢出。Pane 中的任何可滚动内容,都需要在其根元素上使用完整的 flex-1 min-h-0 overflow-hidden 链:

<Resizable.Pane defaultSize={80}>
  
  <div class="flex flex-1 min-h-0 flex-col overflow-hidden">
    <div class="border-b">Header</div>
    <div class="flex-1 overflow-y-auto">
      
    </div>
  </div>
</Resizable.Pane>
使用 ScrollArea(bits-ui)

ScrollArea.Root 渲染时使用 position: relative,其视口则使用 height: 100%。这会破坏 flex 尺寸计算链:视口的百分比高度会相对于 relative 父元素计算,而该父元素在 flex 上下文中没有显式高度。内容会扩展,而不是滚动。

有两种选择:

  1. 优先使用普通的 overflow-y-auto,应用于带有 flex-1 min-h-0 的 div(更简单,始终有效)
  2. 如果需要带样式的滚动条,将 ScrollArea.Root 包装在带有 flex-1 min-h-0 overflow-hidden 的 div 中,为其提供有界祖先

<div class="flex-1 overflow-y-auto">
  {#each items as item}
    <div>{item.name}</div>
  {/each}
</div>


<div class="flex-1 min-h-0 overflow-hidden">
  <ScrollArea.Root class="h-full">
    {#each items as item}
      <div>{item.name}</div>
    {/each}
  </ScrollArea.Root>
</div>
经验法则

如果你在一个同一 flex 列中有兄弟元素的 flex 子元素上写了 h-full,请停下来,将其替换为 flex-1 min-h-0 overflow-hidden。h-full 模式仅在该元素是其 flex 父元素的唯一子元素时才有效。

评论

支持 Markdown