) {
event.preventDefault()
const data = new FormData(event.currentTarget)
const name = String(data.get('name') ?? '').trim()
if (!name) {
setError('名称不能只包含空格。')
return
}
setError('')
setResult(`已保存 ${name},同步方式 ${data.get('sync')}`)
}
return (
)
}
```
### 受控输入与开关
```tsx example=controlled-inputs
import { useId, useState } from 'react'
import { Field, GroupBox, RadioButton, Slider, Switch, TextBox } from '@a1knla/cakeui'
export function Preferences() {
const id = useId()
const [name, setName] = useState('miku')
const [enabled, setEnabled] = useState(true)
const [volume, setVolume] = useState(60)
const [mode, setMode] = useState('manual')
return (
setName(event.currentTarget.value)} />
setEnabled(event.currentTarget.checked)}>
启用声音
setVolume(event.currentTarget.valueAsNumber)}
/>
setMode(event.currentTarget.value)}
>
手动
setMode(event.currentTarget.value)}
>
自动
)
}
```
### Tabs、原生表格与应用分页
```tsx example=table-and-tabs
import { useState } from 'react'
import {
Pagination,
Tab,
TabList,
TabPanel,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tabs,
} from '@a1knla/cakeui'
const files = [
{ id: '1', name: '说明.md', kind: '文档' },
{ id: '2', name: '标志.svg', kind: '设计' },
{ id: '3', name: '设置.json', kind: '配置' },
]
export function FileWorkspace() {
const [tab, setTab] = useState('files')
const [page, setPage] = useState(1)
const pageSize = 2
const visible = files.slice((page - 1) * pageSize, page * pageSize)
return (
文件
动态
工作空间文件
名称
类型
{visible.map((file) => (
{file.name}
{file.kind}
))}
`第 ${value} 页`}
/>
暂无动态。
)
}
```
### Interactive 菜单打开受控 Dialog
```tsx example=interactive-dialog
import { type FormEvent, useId, useRef, useState } from 'react'
import { Button, Card, ContextMenu, Dialog, Field, MenuItem, MenuSeparator, TextBox } from '@a1knla/cakeui'
export function RenameResource() {
const id = useId()
const input = useRef(null)
const [name, setName] = useState('设计资源')
const [draft, setDraft] = useState(name)
const [open, setOpen] = useState(false)
function startRename() {
setDraft(name)
setOpen(true)
}
function save(event: FormEvent) {
event.preventDefault()
if (!draft.trim()) return
setName(draft.trim())
setOpen(false)
}
return (
<>
>
}
>
{name}
>
)
}
```
上例的 F2 仅是菜单提示,没有绑定快捷键;触屏用户可用可见“重命名”按钮。Dialog 与 ContextMenu 为同级节点,避免把 Dialog 嵌入会参与菜单键盘事件冒泡的菜单内容。
### 日志追加与单条通知
```tsx example=logs-and-toast
import { useState } from 'react'
import { Button, LogEntry, type LogEntryProps, LogView, Toast } from '@a1knla/cakeui'
interface Entry {
id: number
time: string
dateTime: string
level: NonNullable
message: string
}
export function ActivityLog() {
const [entries, setEntries] = useState([])
const [notice, setNotice] = useState({ open: false, key: 0 })
function append() {
const now = new Date()
setEntries((current) => [
...current.slice(-199),
{
id: (current.at(-1)?.id ?? 0) + 1,
time: now.toLocaleTimeString(),
dateTime: now.toISOString(),
level: 'success',
message: '任务完成',
},
])
setNotice((current) => ({ open: true, key: current.key + 1 }))
}
return (
<>
{entries.map((entry, index) => (
0 && entry.time === entries[index - 1].time ? undefined : entry.time}
>
{entry.message}
))}
setNotice((current) => ({ ...current, open }))}
duration={4500}
tone="success"
closeLabel="关闭通知"
>
任务完成
>
)
}
```
示例限制保留 200 条日志,限制来自应用代码,LogView 不会自动截断。用于可复用的子组件示例省略重复 CSS import,宿主入口仍须引入一次样式。
## 6. 主题、变量与动效
三种配色分别有 light / dark 变体。可以在同一页面展示多个 Provider,菜单与 Dialog 保留在自己的主题 DOM 作用域中。全量变量与六套主题的精确值见自动生成附录。
| 变量组 | 用途 |
| ---------------------------------------------------------------------------- | ------------------------------------------------- |
| --cake-bg / --cake-surface | 页面与卡片、弹层表面 |
| --cake-control / --cake-control-hover | 普通控件常态与悬停背景 |
| --cake-text / --cake-muted | 正文与辅助文字 |
| --cake-line / --cake-edge | 分隔线与内描边 |
| --cake-hover / --cake-backdrop / --cake-shadow | 轻悬停背景、模态遮罩、弹层阴影 |
| --cake-accent / --cake-accent-soft / --cake-accent-hover / --cake-accent-ink | 主题色、填充、悬停填充与填充上的文字 |
| --cake-success / --cake-warning / --cake-danger 及各自 -soft | 状态前景与背景 |
| --cake-radius-small / -medium / -large / -dialog | 圆角,默认 10 / 14 / 20 / 26px |
| --cake-control-height | 普通控件高度基线,comfortable 38px / compact 32px |
| --cake-font / --cake-mono | 普通字体与日志、代码等宽字体 |
| --cake-fast / --cake-normal / --cake-slow / --cake-ease | 150 / 200 / 250ms 与缓出曲线 |
`--cake-tone` / `--cake-tone-soft` 是组件内部按 tone 计算的派生变量,推荐修改对应主题或状态变量而不是把它们当成全局配色入口。
定制可用 Provider 的 style 设置变量(TypeScript 可用交叉类型为自定义属性建模),或者在引入 @a1knla/cakeui/style.css 之后加载应用样式,用自己的 className 调整。主题选择器可能有更高 specificity;覆盖具体主题颜色时可以限定自己的 `.app-theme.cake-theme`,需要超过带 data 属性的规则时增加明确的主题属性选择器。不要用大量 !important 遮盖层叠问题。
```scss
.app-theme.cake-theme {
--cake-font: 'Noto Sans SC', system-ui, sans-serif;
--cake-radius-small: 8px;
}
.app-theme.cake-theme[data-theme='blue'] {
--cake-accent: #426b91;
}
```
字体按本机已安装字体回退,不下载远程字体。默认顺序包含 Inter Variable、SF Pro Text、Noto Sans SC、PingFang SC、Segoe UI Variable Text、system-ui;等宽字体使用 Cascadia Code / Consolas / monospace。
默认 Button 的按压与 Interactive 区域下陷幅度均为 1px,不能为了加重效果给它们再叠加独立 transform 而不检查命中与定位。输入 focus 只保留一圈内描边;如果集成后出现双边框,检查消费项目的全局 input、focus、outline 和 box-shadow 规则。
prefers-reduced-motion: reduce 将三个时长变量设为 0,并关闭 Spinner / Skeleton 的持续动画。业务状态与事件不等待 transitionend 或 animationend;即使动画关闭也应立即完成选择、关闭、提交和焦点恢复。
## 7. 可访问性、交互验证与排错
集成时逐项考虑:原生标签关系、图标按钮名称、禁用状态、键盘路径、当前焦点、页面缩放 / 窄视口、深色模式、减少动态效果、需要复制的内容。组件提供基础语义,但不会自动为业务内容补全名字、翻译英文标签或建立数据关系。
| 现象 | 优先检查 |
| ----------------------- | ------------------------------------------------------------------------------------ |
| 组件没有样式 | 是否导入 @a1knla/cakeui/style.css,是否安装本仓库打包产物,构建器是否允许 CSS import |
| 配色不是预期 | Provider 默认 blue/system;无 Provider 为 blue/light;Gallery 有独立本地持久化 |
| 无法选择正文 | 根节点默认 user-select:none;给要复制的业务区域显式 user-select:text |
| 输入有两个边框 | 应用 reset / 全局 input 样式叠加了 border 或 outline;检查最终 computed style |
| ComboBox 仍为系统下拉 | 浏览器对 base-select / picker 的支持;多选或 size 路径;是否直接传 option |
| 输入无法编辑或重置 | 受控 value 没有在 onChange 更新,或 reset 只重置 DOM 未重置状态 |
| Field 文字点击不聚焦 | htmlFor 与输入 id 是否一致;复合控件 id 是否落到真实输入上 |
| Tabs 没有内容 | 初始 value 为空、不匹配、被禁用,或外部 CSS 覆盖 hidden |
| MenuItem 点击后不关闭 | onClick 是否 preventDefault;是否在 ContextMenu.menu 的作用域内 |
| 右键松开执行了意外动作 | 以松开实际光标坐标为准,不以焦点为准;不要让业务动作绑定两套事件 |
| Dialog 请求关闭却仍打开 | onOpenChange 是否更新受控 open,或应用校验是否刻意阻止关闭 |
| 提示不能通过键盘打开 | HoverTips 的 child 是否可聚焦且透传 aria-describedby;是否为 disabled 控件 |
| Toast 新内容很快消失 | 仅替换 children 不重置计时;新通知使用新 key 或重新开启 |
| 日志不再自动滚到底 | 用户已经上翻,距离底部不少于 24px;follow 不负责无条件抢回滚动 |
| 构建报告导出不存在 | 对照 src/index.ts 和 Props 附录;不要按其他 UI 库命名猜测 |
目前不提供日期日历组件、树、富文本编辑器、多级菜单、拖拽系统、虚拟列表、Drawer 或 SidePanel。需要这些功能时先说明应用需求,再决定组合原生能力或增加独立实现,不伪造现有导出。
## 8. 仓库结构、开发与验证
```text
ARCH.md 原始设计需求与参考
AGENTS.md 本项目的 AI 协作和文档同步约定
src/index.ts 唯一公开组件 / 类型入口
src/styles.scss 全量 SCSS 入口
src/components/Foo/index.tsx 每个公开组件的实现与 Props
src/components/Foo/index.scss 对应样式
src/components/CakeProvider/ 主题与设计变量
src/styles/_mixins.scss 输入、控件、菜单表面、状态色共享规则
src/hooks/useControllable.ts 内部受控状态辅助,不公开导出
src/focus.ts 内部焦点停靠点查询
demo/App/components/ Gallery 专用展示与组合示例
docs/ai.md 本文人工维护源
scripts/docs.mjs 纯文本生成与一致性检查
public/llms.txt 生成的简短索引
public/llms-full.txt 生成的正文 + 公开类型 + 主题变量
tests/components.test.tsx 单元 / DOM 行为验证
tests/browser/ 真实 Chrome 交互与可访问性验证
scripts/package-smoke.mjs 真实 tgz 消费、文档示例类型检查
deploy/ Nginx 与远端安装脚本
scripts/deploy-gallery.ps1 Windows 部署入口
```
```sh
npm run dev # 本地 Gallery,通常为 http://127.0.0.1:5173
npm run docs:build # 从正文与源码重建两个纯文本文件
npm run docs:check # 检查生成结果、组件覆盖与源码一致,不修改文件
npm run typecheck # src / demo / tests 严格 TypeScript 检查
npm test # DOM、原生表单、受控状态、键盘、计时和滚动
npm run build # 组件库 ESM、声明文件与 CSS
npm run build:demo # 检查文档后输出静态 Gallery,包含两个纯文本文件
npm run test:browser # Chrome 真实交互、窄屏与 axe 检查
npm run test:package # 打包、离线安装、公开导出、SSR、文档示例类型与消费构建
npm run format # 项目格式化
npm run check # 文档、类型、单元测试、库与 Gallery 构建、格式检查
```
Windows 浏览器测试使用已安装 Google Chrome,其他系统需要先安装 Playwright Chromium。DOM 测试不能代替原生 dialog / Popover / picker 的真实浏览器验证。自动 axe 检查不能代替完整辅助技术人工验证。
编码保持 TypeScript + SCSS,每个组件一个目录。具名函数用 function;匿名回调用箭头函数。样式为需要样式的元素分配 className,显式写出完整类名,不用 BEM、不用 `&-suffix` 拼接,不靠匿名标签后代选择器挂样式。优先 flex / grid;共享能力有真实复用需求后再提升。
不使用 worktree。默认在当前分支工作,不为了普通改动创建分支。commit / push 前确认项目署名为 miku,不添加 AI / Codex 署名或 Co-Authored-By。完整协作规则见仓库根 AGENTS.md。
## 9. 文档发布与后续维护
每次改动都必须判断文档是否需要同步。公开 API、默认值、DOM / ref 目标、事件与关闭规则、主题变量、键盘或无障碍行为、兼容性、安装构建、Gallery 文档入口或部署方式发生变化时,更新相关文档与示例。纯内部重构若不改变这些内容,可以不改正文,但在交付说明中明确判断依据。不要为了形式要求制造无意义的文档改动。
文档维护顺序:核对源码与测试 → 更新 docs/ai.md 对应段落 / 示例 → 必要时同步 README.md、docs/design.md、docs/deployment.md → 格式化 → npm run docs:build → npm run docs:check。新增公开组件必须有同名三级标题,并有具体的属性和行为说明。类型和主题变量附录自动生成,不手工编辑 public/llms*.txt。自动检查可以发现生成物过期、公开组件漏写等机械问题,不能代替维护者判断正文是否准确。
Gallery 的“AI 技术文档”阅读页与纯文本使用同一份生成结果。全文是 UTF-8 的 text/plain,内部使用 Markdown 组织章节,包含代码和表格,但不是 HTML 页面。直接 GET 即可读取,不需要登录、运行 JavaScript 或先打开 Gallery。llms.txt 是短入口,真正生成代码前应读取 llms-full.txt;它们不是要求某个 AI 平台自动发现文档的保证。
Gallery 构建会检查文档一致性,再将 public 中两个文件复制到 demo-dist。部署把 HTML、静态资源和文档作为同一版本上传并切换,避免站点更新却忘记发布新文档。纯文本路径明确使用 text/plain; charset=utf-8、no-cache,缺失文件返回 404 而不是 SPA HTML。
本地 Vite 开发与 preview 通过文档中间件提供相同的 UTF-8 纯文本响应和 404 规则;开发读取 public,preview 读取构建输出目录。不依赖浏览器自行猜测编码。
部署到 https://gallery.vanillacake.cn,复用 Ubuntu 上的 Nginx 和 Certbot / Let's Encrypt。Windows 使用 `./scripts/deploy-gallery.ps1`;已构建且内容未变化时可用 -SkipBuild。详细目录、证书续期和回退步骤见 `docs/deployment.md`。发布后验证 Gallery 链接可见、两条纯文本链接返回 200、Content-Type 正确、正文与本地生成结果一致。
以上记录的是当前实现,不承诺未验证浏览器或未来版本的相同行为。修改库时同时修改描述;集成不同版本时以所安装包的类型与该版本源码为准。
## 附录 A. 当前构建身份与公开导出
- 包名:@a1knla/cakeui
- 版本:0.1.0
- 署名:miku
- React peer:>=19.0.0;React DOM peer:>=19.0.0
- 公开组件:48 个;下列列表与 src/index.ts 一致。
- 文档 / package.json / src 内容指纹(SHA-256):6075e17f7cc8453d91dbcc4495ee5c5676b61993bb5495373d300f2483ebbc24
- 本文为生成结果,请修改 docs/ai.md 或相应源码后运行 npm run docs:build。
Accordion, AccordionItem, Alert, Avatar, Badge, Breadcrumb, BreadcrumbItem, Button, CakeProvider, Card, CheckBox, ComboBox, ContextMenu, Dialog, Dot, Field, GroupBox, HoverTips, ListItem, ListView, LogEntry, LogView, MenuItem, MenuSeparator, NumberBox, Pagination, ProgressBar, RadioButton, Separator, Skeleton, Slider, Spinner, Switch, Tab, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TabList, TabPanel, Tabs, Tag, TextArea, TextBox, Toast, WhatsThis
## 附录 B. 完整公开 Props 与类型
下列声明直接提取自公开入口所导出的源文件;原生属性由 React 类型扩展,不在这里重复 React 的完整 DOM 类型。组件对应同名 Props,例如 Button 使用 ButtonProps。默认值与行为见正文,类型中的可选标记不表达默认值。
```typescript
import type { ComponentPropsWithRef, ComponentPropsWithoutRef, ReactElement, ReactNode, RefObject } from 'react'
export type AccordionProps = ComponentPropsWithRef<'div'>
export interface AccordionItemProps extends Omit, 'title'> {
title: ReactNode
}
export interface AlertProps extends ComponentPropsWithRef<'div'> {
tone?: Tone
}
export interface AvatarProps extends ComponentPropsWithRef<'span'> {
src?: string
name: string
size?: Size
}
export interface BadgeProps extends ComponentPropsWithRef<'span'> {
tone?: Tone
}
export type BreadcrumbProps = ComponentPropsWithRef<'nav'>
export type BreadcrumbItemProps = ComponentPropsWithRef<'li'>
export interface ButtonProps extends ComponentPropsWithRef<'button'> {
variant?: 'default' | 'primary' | 'ghost' | 'danger'
size?: Size
loading?: boolean
}
export type CakeTheme = 'blue' | 'pink' | 'gold'
export type CakeMode = 'light' | 'dark' | 'system'
export interface CakeProviderProps extends ComponentPropsWithRef<'div'> {
theme?: CakeTheme
mode?: CakeMode
density?: 'comfortable' | 'compact'
}
export interface CardProps extends ComponentPropsWithRef<'div'> {
padding?: 'none' | 'small' | 'medium' | 'large'
}
export interface CheckBoxProps extends Omit, 'type' | 'size'> {
indeterminate?: boolean
}
export type ComboBoxProps = ComponentPropsWithRef<'select'>
export interface ContextMenuProps extends ComponentPropsWithoutRef<'div'> {
menu: ReactNode
menuLabel: string
interactive?: boolean
}
export interface DialogProps extends Omit, 'open' | 'title'> {
open: boolean
onOpenChange: (open: boolean) => void
title: ReactNode
description?: ReactNode
footer?: ReactNode
closeLabel?: string
closeOnBackdrop?: boolean
initialFocus?: RefObject
}
export interface DotProps extends ComponentPropsWithRef<'span'> {
tone?: Tone
}
export interface FieldProps extends ComponentPropsWithRef<'div'> {
label: ReactNode
htmlFor: string
description?: ReactNode
error?: ReactNode
}
export interface GroupBoxProps extends ComponentPropsWithRef<'fieldset'> {
label: ReactNode
}
export interface HoverTipsProps {
children: ReactElement<{ 'aria-describedby'?: string }>
content: ReactNode
delay?: number
placement?: 'top' | 'bottom'
className?: string
}
export type ListItemProps = ComponentPropsWithRef<'li'>
export type ListViewProps = ComponentPropsWithRef<'ul'>
export interface LogEntryProps extends ComponentPropsWithRef<'div'> {
time?: string
dateTime?: string
level?: 'info' | 'success' | 'warning' | 'error' | 'debug'
}
export interface LogViewProps extends ComponentPropsWithoutRef<'div'> {
follow?: boolean
}
export interface MenuItemProps extends ComponentPropsWithRef<'button'> {
shortcut?: ReactNode
danger?: boolean
}
export type MenuSeparatorProps = ComponentPropsWithRef<'div'>
export type NumberBoxProps = Omit, 'type'>
export interface PaginationProps extends ComponentPropsWithRef<'nav'> {
page: number
count: number
onPageChange: (page: number) => void
previousLabel?: string
nextLabel?: string
pageLabel?: (page: number) => string
}
export type ProgressBarProps = ComponentPropsWithRef<'progress'>
export type RadioButtonProps = Omit, 'type' | 'size'>
export type SeparatorProps = ComponentPropsWithRef<'hr'>
export type SkeletonProps = ComponentPropsWithRef<'span'>
export type SliderProps = Omit, 'type'>
export interface SpinnerProps extends ComponentPropsWithRef<'span'> {
size?: Size
}
export type SwitchProps = Omit, 'type' | 'size'>
export interface TabProps extends Omit, 'value'> {
value: string
}
export type TableProps = ComponentPropsWithRef<'table'>
export type TableBodyProps = ComponentPropsWithRef<'tbody'>
export type TableCellProps = ComponentPropsWithRef<'td'>
export type TableHeadProps = ComponentPropsWithRef<'thead'>
export type TableHeaderProps = ComponentPropsWithRef<'th'>
export type TableRowProps = ComponentPropsWithRef<'tr'>
export type TabListProps = ComponentPropsWithRef<'div'>
export interface TabPanelProps extends ComponentPropsWithRef<'div'> {
value: string
}
export interface TabsProps extends Omit, 'onChange' | 'defaultValue'> {
value?: string
defaultValue?: string
onValueChange?: (value: string) => void
orientation?: 'horizontal' | 'vertical'
}
export interface TagProps extends ComponentPropsWithRef<'span'> {
tone?: Tone
onRemove?: () => void
removeLabel?: string
}
export type TextAreaProps = ComponentPropsWithRef<'textarea'>
export type TextBoxProps = ComponentPropsWithRef<'input'>
export interface ToastProps extends ComponentPropsWithoutRef<'div'> {
open: boolean
onOpenChange: (open: boolean) => void
duration?: number
tone?: Tone
closeLabel?: string
}
export interface WhatsThisProps {
children: ReactNode
label?: string
}
export type Size = 'small' | 'medium' | 'large'
export type Tone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger'
```
## 附录 C. 从 SCSS 生成的主题变量
以下取 comfortable 密度、未开启减少动态效果时的值。compact 与 reduced-motion 的覆盖规则见正文。
### 共用变量
| 变量 | 值 |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `--cake-control-height` | `38px` |
| `--cake-ease` | `cubic-bezier(0.29, 0, 0, 1)` |
| `--cake-edge` | `inset 0 0 0 1px var(--cake-line)` |
| `--cake-fast` | `150ms` |
| `--cake-font` | `'Inter Variable', 'SF Pro Text', 'Noto Sans SC', 'PingFang SC', 'Segoe UI Variable Text', system-ui, sans-serif` |
| `--cake-mono` | `'Cascadia Code', 'Consolas', monospace` |
| `--cake-normal` | `200ms` |
| `--cake-radius-dialog` | `26px` |
| `--cake-radius-large` | `20px` |
| `--cake-radius-medium` | `14px` |
| `--cake-radius-small` | `10px` |
| `--cake-slow` | `250ms` |
### 六套配色的变量
| 变量 | blue / light | blue / dark | pink / light | pink / dark | gold / light | gold / dark |
| ---------------------- | --------------------------------------------- | ----------------------------------------- | --------------------------------------------- | ----------------------------------------- | --------------------------------------------- | ----------------------------------------- |
| `--cake-accent` | `#416b97` | `#b5d2ef` | `#93566e` | `#efc3d4` | `#8b654a` | `#e8c8ad` |
| `--cake-accent-hover` | `#c7ddf2` | `#4a6d90` | `#ebc9d8` | `#825366` | `#efc9aa` | `#80614d` |
| `--cake-accent-ink` | `#294866` | `#f4f9ff` | `#653b4b` | `#fff7fa` | `#513b2e` | `#fff7ef` |
| `--cake-accent-soft` | `#d6e7f7` | `#3e5e7d` | `#f1d8e3` | `#71495a` | `#f3d3b9` | `#715543` |
| `--cake-backdrop` | `#30394238` | `#0007` | `#30394238` | `#0007` | `#30394238` | `#0007` |
| `--cake-bg` | `#f6f8fb` | `#202226` | `#faf7f9` | `#231f22` | `#f8f5ef` | `#211f1c` |
| `--cake-control` | `#ffffffb8` | `#ffffff12` | `#ffffffb8` | `#ffffff12` | `#ffffffb8` | `#ffffff12` |
| `--cake-control-hover` | `#fff` | `#ffffff1c` | `#fff` | `#ffffff1c` | `#fff` | `#ffffff1c` |
| `--cake-danger` | `#934a53` | `#efb8be` | `#934a53` | `#efb8be` | `#934a53` | `#efb8be` |
| `--cake-danger-soft` | `#f3dde0` | `#9a596133` | `#f3dde0` | `#9a596133` | `#f3dde0` | `#9a596133` |
| `--cake-hover` | `#2b384509` | `#ffffff0b` | `#2b384509` | `#ffffff0b` | `#2b384509` | `#ffffff0b` |
| `--cake-line` | `#2b38451a` | `#f4f7fa1a` | `#2b38451a` | `#f4f7fa1a` | `#2b38451a` | `#f4f7fa1a` |
| `--cake-muted` | `#626d79` | `#aab1b9` | `#626d79` | `#aab1b9` | `#626d79` | `#aab1b9` |
| `--cake-shadow` | `0 0 0 1px #2b384514, 0 36px 100px #2b38451f` | `0 0 0 1px #ffffff14, 0 36px 100px #0005` | `0 0 0 1px #2b384514, 0 36px 100px #2b38451f` | `0 0 0 1px #ffffff14, 0 36px 100px #0005` | `0 0 0 1px #2b384514, 0 36px 100px #2b38451f` | `0 0 0 1px #ffffff14, 0 36px 100px #0005` |
| `--cake-success` | `#46654e` | `#a9c8af` | `#46654e` | `#a9c8af` | `#46654e` | `#a9c8af` |
| `--cake-success-soft` | `#dce9df` | `#7b9b8233` | `#dce9df` | `#7b9b8233` | `#dce9df` | `#7b9b8233` |
| `--cake-surface` | `#fdfefe` | `#2d3035` | `#fdfefe` | `#332d32` | `#fdfefe` | `#312e2a` |
| `--cake-text` | `#29313a` | `#edf0f3` | `#29313a` | `#edf0f3` | `#29313a` | `#edf0f3` |
| `--cake-warning` | `#80602d` | `#e2c18f` | `#80602d` | `#e2c18f` | `#80602d` | `#e2c18f` |
| `--cake-warning-soft` | `#f3e7ce` | `#b68c4133` | `#f3e7ce` | `#b68c4133` | `#f3e7ce` | `#b68c4133` |