CodeGym /课程 /ChatGPT Apps /视觉设计:主题、配色、排版、间距与现成库

视觉设计:主题、配色、排版、间距与现成库

ChatGPT Apps
第 8 级 , 课程 3
可用

1. 背景:你的 App 是 ChatGPT 家中的客人

在画按钮、选字体之前,先接受一个现实:用户并未打开“你的网站”,他正身处 ChatGPT。ChatGPT 已经自带了:

  • 配色方案,
  • 字体与字号,
  • 间距与元素布局。

你的小部件会展示在这个环境中,通常是在 iframe 里。重要结论是:视觉上 App 应该像 ChatGPT 界面的自然延伸,而不是一个来自 2008 年的横幅。

OpenAI 的官方指南正是强调这一点:不要破坏系统颜色与字体,只做克制的品牌强调,并遵循平台的基本排版与间距网格。

落到实操,这意味着三件事。

首先,背景、基础文字颜色、标准排版——都应当从 ChatGPT 或系统变量继承,而不是“我就是这么想的”。

其次,如果你想要“自己的风格”,请把它集中在强调部件上:主要按钮、徽章、强调态等,而不是彩虹背景或自定义的 Comic Sans 字体——哪怕你内心再想。

第三,同一个 App 的 inline 与 fullscreen 模式在视觉上必须是一致的世界:相同的 CTA 颜色、相同的卡片圆角与间距、相同的排版。用户从 inline 切到 fullscreen 时不该感觉进入了另一个产品。

接下来按层来拆:颜色与主题、排版、间距与网格,然后看看 Tailwindshadcn/ui 如何把它们拼起来。

洞察

ChatGPT sandbox 不仅限制你小部件的功能,还会给它加上自己的样式。

首先——HTML 根元素

站点原始代码:

<html lang="ru">

在沙盒中:

<html lang="en-US" data-theme="light" class="light" style="--safe-area-inset-top: 0px; --safe-area-inset-bottom: 0px; --safe-area-inset-left: 0px; --safe-area-inset-right: 0px;">

其次——系统自带的 CSS 样式,让你的小部件更像 ChatGPT:

<style>
  html,body,#root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:0;padding:0}
  html,body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Helvetica Neue,Arial,sans-serif!important}
  button,input,textarea,select{font-family:inherit}
  html{background-color:#fff}
  html.dark{background-color:#212121}
  html.mobileSkybridge.dark{background-color:#000}
  @supports (font: -apple-system-body){html.mobileSkybridge{font:-apple-system-body}}
</style>

请牢记这一点——意外会更少。

2. 主题与配色:同时生活在浅色与深色宇宙

浅色与深色主题

ChatGPT 界面已经支持浅色与深色主题。你的小部件会展示在其中之一,且用户可以随时切换。因此,任何硬编码的纯白或纯黑背景都可能是地雷。

想象一个小部件,白底黑字。在浅色主题下还能接受;在深色主题里就像探照灯直射眼睛。反过来,在浅色主题里硬上黑底也不妙。这就是为什么官方建议不要硬编码颜色,而应依赖宿主的主题/变量。

在 Apps SDK 的环境中,通常会提供当前主题的 API 或 CSS 变量。文档中会出现类似 window.openai.theme 的方式,以及使用 ChatGPT 的标准 CSS 变量。当然也可以利用 prefers-color-schemeTailwinddark: 工具类。

核心思路:你的小部件应根据宿主主题自动调整下列内容:

  • 卡片背景(比底色略浅/略深),
  • 文本颜色(满足足够对比度),
  • 边框、阴影与 hover 状态。

一个基于 Tailwind 的最简单主题包裹:

// components/AppShell.tsx
export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <div className="bg-background text-foreground">
      {/* bg-background/text-foreground 由主题重写 */}
      {children}
    </div>
  );
}

其中 bg-backgroundtext-foreground 并非 Tailwind 的默认类,而是你设计系统(例如 shadcn/ui)中映射到 CSS 变量的别名,这些变量再与 ChatGPT 的浅/深主题绑定。

系统颜色 vs 品牌强调

OpenAI 明确表示:ChatGPT 的系统颜色不能改。基础文字、标准聊天面板、背景——都应保持平台的通用配色。你的发挥空间在于小部件内部的强调:CTA 按钮(call to action——主要操作)、徽章、小的元素。

在 GiftGenius 的实践中,这意味着:

  • 礼物卡片的底色接近系统背景,
  • 文字使用系统常规颜色,就像聊天里的文本,
  • GiftGenius 的品牌色用于“选择礼物”这类主要按钮,或用于折扣徽章。

可以想成一张表:

元素 应该做 应避免
小部件背景 从 ChatGPT 继承 使用刺眼的品牌渐变
主要文本 继承系统颜色 上成花里胡哨或灰到不可读
主要 CTA 按钮 使用品牌强调色 在按钮上画“彩虹”与五种颜色
次级按钮/链接 接近系统链接样式 做得和 CTA 一样醒目
阴影/边框 克制、极简 又粗又霓虹的描边

一个设置主色的小例子(Tailwind):

// styles/globals.css(片段)
:root {
  --gift-accent: 222 84% 56%; /* hsl */
}

.dark {
  --gift-accent: 222 84% 64%; /* 暗色模式下稍微更亮一点 */
}
// components/GiftButton.tsx
export function GiftButton({ children }: { children: React.ReactNode }) {
  return (
    <button className="rounded-md bg-[hsl(var(--gift-accent))] px-4 py-2 text-sm font-medium text-white hover:opacity-90">
      {children}
    </button>
  );
}

你不动整个小部件的背景,只把品牌色用在主要 CTA 按钮上,恰到好处。

对比度与 WCAG:务实达标

即使不准备参加 WCAG 的考试,也有一个朴素标准:文字要易读。字号越小,对比度越要高。无障碍课程建议正文与背景的对比度不低于约 4.5:1。这里不深挖标准细节,只保留一个实践原则——保证文本与背景的足够对比。

落地建议:

  • 别为了“优雅”用浅灰字配浅灰背景;
  • 深色主题不要用深灰字配几乎全黑背景;
  • 起码用肉眼检查:如果你需要眯眼,用户也会难受。

可以和自己约定:任何次要文字(说明、提示)也必须可读,只是颜色和字号更低调,而不是“透明到消失”。

3. 排版:系统字体、层级与常识

用系统字体而非自定义字体

官方指南提倡使用平台的系统字体,如 SF Pro、Roboto 等,而不要塞入自家的 webfont。原因不仅是性能,更因为你的 App 要看起来像界面里的原生元素。

在 Next.js 应用里,让小部件内部继承系统字体最简单。在 Tailwind 中通常已设置为 font-sans。如果你想更显式:

// app/layout.tsx(片段)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className="font-sans antialiased">
        {children}
      </body>
    </html>
  );
}

不需要通过 Google Fonts 接三套字体。对教学用的 GiftGenius 来说,严谨的系统字体会比某种花体(如 Lobster)更干净。

层级与字号

我们只需少量排版层级:区块标题、副标题/关键参数、正文与注释。

以 GiftGenius 的 inline 卡片为例,可以约定如下层级:

角色 Tailwind 类 示例
卡片标题
text-base font-semibold
礼物名称
关键参数
text-sm font-medium
价格或类别
描述
text-sm text-muted-foreground
简短描述
注释/次要信息
text-xs text-muted-foreground
配送、商店

一个小的卡片组件:

// components/GiftCard.tsx
type GiftCardProps = {
  title: string;
  price: string;
  description: string;
};

export function GiftCard({ title, price, description }: GiftCardProps) {
  return (
    <div className="rounded-lg border bg-card p-4">
      <h3 className="text-base font-semibold">{title}</h3>
      <p className="mt-1 text-sm font-medium text-emerald-600">{price}</p>
      <p className="mt-2 text-sm text-muted-foreground">{description}</p>
    </div>
  );
}

这里:

  • 没有巨大 H1;
  • 信息紧凑;
  • 层级通过字号与字重清晰可见。

对齐与行长

聊天界面通常较窄,尤其在 inline 中。因此无需在排版上过度设计:左对齐、40–60 个字符一行的长度就很舒服。

好习惯:

  • 不要把卡片里的长文本居中,阅读更费劲;
  • 不要把英文全都用大写;
  • 基础字号不要低于 14 px(Tailwind 的 text-sm),除非有充分理由。

不确定时记住这一点:读者多半是地铁里用手机的疲惫的人,而不是坐在 27 寸显示器前的你。

4. 间距、密度与网格

如果说颜色与字体是“颜料”,那么间距就是空气。缺了它们,再精致的卡片也会变成一团糟。

OpenAI 的建议强调:元素不要“粘在一起”,最好使用设计系统或 UI 框架(Tailwindshadcn/ui 等)的间距与圆角值,并尽量减少水平滚动。

“留白”原则

最简单的模式:采用统一的间距刻度(例如 4 px 或 8 px 递进),不要每次都“想一个新尺寸”。Tailwind 已内置了这些:p-2p-3p-4gap-3 等。

一个 inline 礼物列表的小网格:

// components/GiftListInline.tsx
export function GiftListInline({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex flex-col gap-3">
      {children}
    </div>
  );
}

每张卡片通过 gap-3 分隔,内部使用 p-4,这就足以避免列表像“一张长布单”。

列数:inline vs fullscreen

Apps SDK 的 UX 文档建议 inline 小部件保持 1–2 列卡片;fullscreen 在宽度足够时可以用 2–3 列。

原因很简单:聊天窗口宽度有限,尤其在移动端,两列已接近可读性边界;fullscreen 则可以利用更宽的屏幕更紧凑地排列内容。

示意:

flowchart LR
  subgraph Inline
    A[1 列
窄屏] B[2 列
桌面端] end subgraph Fullscreen C[2 列
主要场景] D[3 列
适用于网格/目录] end

GiftGenius 在 Tailwind 中的实现:

// components/GiftGrid.tsx
export function GiftGrid({ fullscreen, children }: { fullscreen?: boolean; children: React.ReactNode }) {
  const base = fullscreen ? "grid-cols-2 md:grid-cols-3" : "grid-cols-1 sm:grid-cols-2";

  return (
    <div className={`grid gap-4 ${base}`}>
      {children}
    </div>
  );
}

inline 模式下,移动端 1 列、更宽屏幕 2 列;fullscreen 则根据宽度直接采取 2–3 列。

避免水平滚动

聊天天然是纵向的。用户习惯向下滚,而不是左右滑。因此:

  • 尽量让表格与卡片在容器宽度内排布;
  • 不要给弹性容器里的元素设定 width: 600px; 这种固定宽度;
  • max-w-fulloverflow-x-auto 只作为“最后手段”,别当默认。

对 GiftGenius 的卡片而言,使用 w-full 并让网格自行决定每行放多少个会更合适。

5. ChatGPT 容器内的自适应

在常规前端里你可以完全控制 viewport。到了 ChatGPT,这个控制被限制:你的小部件被安放在聊天容器内,而它有自己的大小与规则。Apps SDK 提供了若干有用的桥梁:最大高度、安全区域、设备类型等。

maxHeight 与垂直限制

在 inline 模式下,ChatGPT 可能会限制小部件的高度,以免它“吃掉”整个屏幕。类似 useMaxHeight() 的钩子可以让你知道当前可占用的空间,并在需要的地方加上内部滚动。

伪代码:

// 伪代码,非真实 API:
const maxHeight = useMaxHeight();

return (
  <div style={{ maxHeight, overflowY: "auto" }}>
    <GiftGrid>{/* ... */}</GiftGrid>
  </div>
);

这样可以避免小部件“顶到”屏幕底部,导致聊天消息滚到“前尘往事”里。

safeArea 与移动设备

在移动设备上,上下可能有刘海、状态栏、系统面板。Apps SDK 允许获取 safeArea,从而调整内边距,避免内容跑到手机“刘海”下方。

在 CSS 层可以加额外的内边距:

// 伪代码
const { top, bottom } = useSafeArea(); // 例如返回 { top: 8, bottom: 16 }

return (
  <div style={{ paddingTop: top, paddingBottom: bottom }}>
    {/* 内容 */}
  </div>
);

本讲更关注原则:小部件要尊重高度限制与安全区域,否则 UX 会立刻变成“再多滚三次才能看到按钮”。

6. Tailwind 与 shadcn/ui:别重新发明按钮

如今全用原生 CSS 手搓 UI 几乎是硬核运动。在 ChatGPT Apps 的语境下,选一个成熟的库并按平台要求调整更高效。本课程把 Tailwindshadcn/ui 作为基础栈。

把 Tailwind 当作间距与颜色的词典

Tailwind 提供了便利的工具集:

  • 间距(如 p-4gap-3),
  • 字号(如 text-smtext-base),
  • 颜色(如 text-muted-foregroundbg-card),在 shadcn/ui 等系统中已绑定到主题的 CSS 变量。

这与 ChatGPT 的要求天然契合:

  • 你不会随意编造间距;
  • 字号设置一致;
  • 不破坏系统颜色,使用预先约定的 token。

shadcn/ui:一组精致的组件

shadcn/ui(以及类似库)提供现成的 CardButtonInputTabs 等组件,并与 Tailwind 主题联动。这能显著加快搭建简洁、极简的界面,尤其适合 GiftGenius 的卡片。

使用 shadcn/ui 的 GiftCard 示例:

// components/GiftCardShadcn.tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

type GiftCardProps = {
  title: string;
  price: string;
  description: string;
};

export function GiftCardShadcn(props: GiftCardProps) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">{props.title}</CardTitle>
      </CardHeader>
      <CardContent className="space-y-2">
        <p className="text-sm font-medium text-emerald-600">{props.price}</p>
        <p className="text-sm text-muted-foreground">{props.description}</p>
        <Button className="mt-2">选择礼物</Button>
      </CardContent>
    </Card>
  );
}

重点不在 shadcn 本身,而在这些原则:

  • 标题不夸张;
  • 描述可读;
  • 按钮按统一设计系统来,而不是“另起炉灶”。

针对 ChatGPT 的定制

在真实项目里,你可以把调色盘调整到 ChatGPT 的极简风:浅色背景、柔和阴影、克制圆角。课程也建议基于既有设计系统,而不是自创宇宙。

简单做法:

  • 采用 shadcn/ui 基础;
  • 保留系统字体;
  • primary/accent token 中配置一到两个品牌色;
  • 确保 inline 与 fullscreen 使用相同的 token。

这样即可获得统一的视觉内核,且事半功倍。

7. GiftGenius 的视觉语言:把一切串起来

我们来梳理一下,哪些可以被视为 GiftGenius 的“视觉语言”。

首先,配色方案。背景与文本继承自 ChatGPT;强调色不夸张但清晰,用于 CTA 按钮与(可选)折扣徽章。在深色主题中,强调色稍提亮以维持对比。

其次,排版。系统字体,text-sm 用于正文,text-base 用于卡片标题。斜体与全大写少用且要有理由。fullscreen 的标题可上调一级,但不要动辄 text-4xl

再次,间距与网格。inline 模式的礼物列表为 1–2 列,使用 gap-3/gap-4,每张卡片设 p-4。fullscreen 模式为 2–3 列;各步骤之间,表单与按钮保持充足间隔。主要场景不出现水平滚动。

GiftGenius 屏幕示意:

graph TD
  A[Inline:礼物列表] --> B[GiftCard
颜色/排版/CTA] A --> C[GiftGrid 1–2 列] D[Fullscreen:选择向导] --> E[第 1 步
表单] D --> F[第 2 步
过滤器/范围] D --> G[第 3 步
确认] B --> H[GiftButton
品牌强调]

最后,与宿主上下文的和谐。所有元素能在浅/深主题间良好切换,尊重 maxHeight,不被 safe-area 遮挡。颜色不与 ChatGPT 冲突;各处的 CTA 按钮外观一致,让用户凭“肌肉记忆”就知道该点哪里。

这样的决策集合,已经足以向真实用户或产品经理演示,而不只是给程序员看:大家可以讨论的不止是“这里是 MCP,那里是 Agents SDK”。

8. 无障碍(Accessibility Guidelines,WCAG AA)

我们在 2.3 节提到过 WCAG,当时只关心一个实践指标——别牺牲可读性。现在把视角放宽:对于看不到界面的人,以及 ChatGPT 的语音模式,同一个界面是怎样被感知与操作的。

WCAG AA 是国际标准 WCAG(Web Content Accessibility Guidelines) 的一个级别,描述如何让网站与界面对视力、动作、认知等不同障碍的人群更可用。

WCAG AA 的核心是把“理论可访问”变成真正可用。该级别包含数十条直接影响交互质量的要求,包括我们谈到的文本与背景对比度约 4.5:1,以及可点击区域大小、焦点状态、表单报错等。

另一个层面是对无障碍技术的支持,包括屏幕阅读器。AA 级要求语义正确:标题就是标题、列表就是列表、按钮就是按钮,交互元素要有正确的角色与文本替代。这让使用 VoiceOver、TalkBack、NVDA 等软件的用户能完整理解结构与含义。

Screen reader(屏幕阅读器)

Screen reader(屏幕阅读器)是一类程序,它会朗读和/或结构化屏幕内容,使视障用户能够使用电脑、手机或 Web 应用。

屏幕阅读器不仅是“把文本读出来”的工具,它是一套与界面交互的完整系统:把网站或应用的可视化呈现转译为可感知的声音结构化导航。

ChatGPT、屏幕阅读器与 WCAG AA

如果你的部件遵循 WCAG AA(正确角色、标题、按钮标签),它不仅对屏幕阅读器友好,对 ChatGPT 的语音模式也更可理解。用户用语音与 ChatGPT 交流,模型基于同样的语义结构,能够“虚拟地”完成人与界面的交互:定位元素、点击按钮、跟随链接等。

根据 ChatGPT Store 的要求,支持 WCAG AA强制性的。每个小部件与 tool 都需要高质量的描述,且标记要符合 WCAG AA:语义正确、标签可读、状态可预期。

因此,WCAG AA 不是“只给特殊人群的功能”,而是底层设计原则,以便 ChatGPT Apps 能与应用良好协作,尤其在语音模式下。

关于语音 UX 场景、语音 vs 文本的差异,以及 ChatGPT Store 的要求,我们会在本模块的其他课程与发布模块中再详细讨论。但这些都建立在你已看到的基础之上:语音模式 = 多模态 + 无障碍(WCAG AA + 屏幕阅读器)

9. ChatGPT App 视觉设计的常见错误

错误 №1:硬编码白/黑背景与文字颜色。
开发者画了白底黑字,没有考虑深色主题。浅色里还能用,深色里就像探照灯,破坏 UX。更好的做法是使用系统颜色与宿主主题(CSS 变量、prefers-color-scheme 或 Apps SDK 的 API),自有颜色只用在强调上。

错误 №2:过度品牌化。
出现亮色渐变背景、自定义字体、花哨边框。小部件成了促销横幅,而不是 ChatGPT 界面的一部分。指南要求相反:极简、“原生”的外观,仅在关键元素(如主要按钮)中克制地使用品牌色。

错误 №3:缺乏排版层级。
要么所有文本同一字号/字重,要么在小卡片里堆三层大标题,还全大写。用户分不清主次:到底是名称、价格还是描述更重要。更好的做法是预先约定 3–4 个层级并坚持使用:标题、关键参数、正文、注释。

错误 №4:元素“黏在一起”,没有间距。
卡片互相紧贴,文字挤到边缘,按钮紧靠文本。桌面端尚可,移动端就成了噪音。建议使用统一的间距刻度(例如 Tailwind 的 p-4gap-3),不要吝啬留白。

错误 №5:在 inline 模式里硬塞 4–5 列。
开发者脑海里还在“电商页面”,在聊天里做四列窄卡片。宽屏上也存疑,移动端更是不可读,还引入水平滚动。inline 小部件通常 1–2 列就够了;第三列留给 fullscreen。

错误 №6:忽视高度限制与安全区域。
小部件画了巨长列表,没有内部滚动,也不理会 maxHeight,结果按钮“沉到屏幕下方”。或者元素躲到移动端的刘海下。应利用最大高度与安全区域数据,合理分配内部高度与留白。

错误 №7:inline 与 fullscreen 的按钮/卡片风格不一致。
inline 里按钮是绿色圆角,fullscreen 里又变成蓝色直角。用户失去统一产品的感觉。应把按钮与卡片的基础样式抽到公共组件/主题,在所有模式下复用。

错误 №8:“作者风”的字体与装饰过度。
为了“好看”引入笨重的 webfont,破坏与 ChatGPT 的视觉一致性,还可能拖慢性能。平台建议使用系统字体与克制排版。如果想表达设计感,不如打磨图标与 microcopy,而非掀起“字体革命”。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION