1. 背景:你的 App 是 ChatGPT 家中的客人
在開始畫按鈕、挑字體之前,先接受一個現實:使用者不是打開「你的網站」,而是待在 ChatGPT 中。ChatGPT 已經有自己的:
- 配色方案,
- 字體與尺寸,
- 間距與元件編排。
你的小工具會顯示在這個環境裡,多數情況下是在 iframe 中。重要結論:視覺上 App 應該像是 ChatGPT 介面的自然延伸,而不是一個從 2008 年拖來的廣告橫幅。
OpenAI 的官方指引正是如此:不要破壞系統的顏色與字體,只加入適度的品牌重點,並遵循平台的基本字體排印與間距網格。
實務上這意味著三件事。
第一,背景、基礎文字色、標準字體排印——這些都應該繼承自 ChatGPT 或系統變數,而不是「我就是藝術家、我想怎麼看就怎麼看」。
第二,如果你想要「自己的風格」,它應該集中在強調處:主要按鈕、徽章、突顯狀態。不要使用彩虹背景或自訂的 Comic Sans 字體——即使你內心很想這麼做。
第三,同一個 App 的 inline 與 fullscreen 模式在視覺上應該屬於同一個世界:相同的 CTA 顏色、相同的卡片圓角與間距、相同的字體排印。使用者不該在從 inline 切換到 fullscreen 時,感覺像換了一個產品。
接著依層次來看:顏色與主題、字體排印、間距與網格,然後談 Tailwind 與 shadcn/ui 如何幫你把這一切組裝起來。
Insight
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-scheme 與 Tailwind 的 dark: 工具類別可用。
核心想法是:你的小工具應該能自動依宿主主題調整以下項目:
- 卡片背景(比基礎背景稍亮/稍暗),
- 文字顏色(足夠對比),
- 邊框、陰影與 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-background 與 text-foreground 並非 Tailwind 預設類別,而是你的設計系統(例如 shadcn/ui)對應到 CSS 變數的別名,並進一步綁定到 ChatGPT 的淺色/深色主題。
系統色 vs. 品牌重點色
OpenAI 說得相當直接:ChatGPT 的系統色不能改。基礎文字、標準聊天面板、背景——都應維持平台的共同色彩。你的舞台在小工具內部的重點:CTA 按鈕(call to action——主要行為)、徽章、小型元素。
在 GiftGenius 的實作中,這代表:
- 禮物卡片的背景接近系統背景,
- 文字使用系統預設色,就像聊天中的文字一樣,
- GiftGenius 的品牌色用在主要的「選擇禮物」按鈕上,也可能用在折扣徽章。
可以想像如下表:
| 元素 | 建議 | 避免 |
|---|---|---|
| 小工具背景 | 繼承 ChatGPT | 放上鮮明的品牌漸層 |
| 主要文字 | 繼承系統色 | 把文字弄成彩色/或灰到看不清 |
| 主要 CTA 按鈕 | 使用品牌重點色 | 在上面畫「彩虹」與 5 種顏色 |
| 次要按鈕/連結 | 接近系統連結樣式 | 做得和 CTA 一樣搶眼 |
| 陰影/邊框 | 柔和、極簡 | 厚重的螢光描邊 |
簡短的 Tailwind 主色設定範例:
// styles/globals.css(片段)
:root {
--gift-accent: 222 84% 56%; /* hsl */
}
.dark {
--gift-accent: 222 84% 64%; /* dark 模式下稍微更亮 */
}
// 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 類別 | 範例 |
|---|---|---|
| 卡片標題 | |
禮物名稱 |
| 關鍵參數 | |
價格或類別 |
| 說明 | |
簡短描述 |
| 附註/小字 | |
配送、商店 |
卡片小元件:
// 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 框架(Tailwind、shadcn/ui 等),並盡量避免水平捲動。
讓版面「會呼吸」的原則
最簡單的做法:使用一致的間距刻度(例如 4 px 或 8 px 為步進),不要每次自己發明尺寸。Tailwind 已內建:p-2、p-3、p-4、gap-3 等。
GiftGenius 的 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 與 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
在 Tailwind 中實作 GiftGenius:
// 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 模式下,手機是一欄、較寬的螢幕給兩欄;在 fullscreen 下,視寬度給 2–3 欄。
避免水平捲動
聊天天性是垂直的。使用者習慣往下捲,而不是往旁邊捲。因此:
- 盡量讓表格與卡片在容器寬度內排好;
- 不要對位於彈性容器內的元素設定像 width: 600px; 這種固定寬度;
- 使用 max-w-full、overflow-x-auto 當作「最後手段」,而不是預設。
對 GiftGenius 的卡片來說,設定 w-full,把「一列能放幾個」交由網格決定會更方便。
5. 在 ChatGPT 容器內的響應式
在一般前端你擁有完整的 viewport 控制;在 ChatGPT 中則受限:你的小工具被放在聊天容器中,容器有自己的尺寸與規則。Apps SDK 提供幾個有用的橋樑:最大高度、安全區域(safe area)、裝置類型等。
maxHeight 與垂直限制
在 inline 模式下,ChatGPT 可能限制小工具高度,以免它「吃掉」整個畫面。像 useMaxHeight() 這類 hook 可以讓你知道目前能佔用的高度,並在需要的地方加上內部捲動。
偽代碼:
// 偽代碼,非真實 API:
const maxHeight = useMaxHeight();
return (
<div style={{ maxHeight, overflowY: "auto" }}>
<GiftGrid>{/* ... */}</GiftGrid>
</div>
);
如此可以避免小工具頂到螢幕下緣,導致聊天訊息「被擠到過去的某個角落」。
safeArea 與行動裝置
在行動裝置上,上下可能有鏡頭缺口、狀態列、系統面板。Apps SDK 允許取得 safeArea,並調整間距,避免內容被手機的「瀏海」遮住。
在 CSS 層級可以加上額外 padding:
// 偽代碼
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 的脈絡下,拿現成的元件庫並依平台要求調整會輕鬆許多。本課程以 Tailwind 與 shadcn/ui 作為基礎堆疊。
Tailwind 是間距與色彩的字典
Tailwind 提供一組很好用的工具類別:
- 間距(p-4、gap-3),
- 尺寸(text-sm、text-base),
- 顏色(text-muted-foreground、bg-card),而在 shadcn/ui 等系統中,這些已經綁定到主題的 CSS 變數。
這與 ChatGPT 的要求完美契合:
- 你不會臨時編造間距;
- 一致地設定文字大小;
- 不會破壞系統色,而是使用事先協調好的代幣。
shadcn/ui 作為一組俐落的元件
shadcn/ui(與同類庫)提供現成的 Card、Button、Input、Tabs 等,並已設定為使用 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 代幣中設定一到兩個品牌色;
- 確保 inline 與 fullscreen 都使用同一組代幣。
如此便能用最少的力氣取得一致的視覺核心。
7. GiftGenius 的視覺語言:把一切整合起來
讓我們系統化整理一下,對於假想的 GiftGenius,可以稱之為「視覺語言」的內容。
第一,配色。背景與文字繼承 ChatGPT;重點色低調但明顯,應用在 CTA 按鈕,可能也用在折扣徽章。深色主題下,重點色稍微更亮以維持對比。
第二,字體排印。基礎系統字體,text-sm 作為主要文字,text-base 作為卡片標題。斜體與全大寫很少用,且只在必要時使用。fullscreen 向導中的標題再往上調一級,但仍不需要誇張的 text-4xl。
第三,間距與網格。inline 模式下,禮物清單為一到兩欄,使用 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 按鈕在各處外觀一致,讓使用者靠肌肉記憶就知道該點哪裡。
這樣的一套決策,已足以把你的應用拿去展示給不只工程師,還有真實使用者或 PM——討論焦點不再只剩下「這邊是 MCP,那邊是 Agents SDK」。
8. 無障礙(Accessibility Guidelines,WCAG AA)
在第 2.3 節談文字與背景對比時,我們已提過 WCAG。當時重點是實用準則——不要犧牲可讀性。現在把視角放寬:對看不見畫面的使用者,以及 ChatGPT 的語音模式來說,同一個介面是什麼樣子。
WCAG AA 是國際標準 WCAG(Web Content Accessibility Guidelines) 中的一個無障礙等級,說明如何讓網站與介面能被不同視覺、動作能力、認知特徵的使用者所使用。
WCAG AA 的主旨是把介面從「理論上可用」變成真正可用。它涵蓋數十條直接影響互動品質的要求,包括我們提過的文字與背景對比約 4.5:1,以及點擊區域大小、焦點狀態、表單錯誤處理等。
另一個層面是支援無障礙技術,包含螢幕閱讀器(screen reader)。AA 等級要求正確語意:標題要是標題、清單要是清單、按鈕要是按鈕,互動元件要有正確的角色與替代文字。如此一來,使用 VoiceOver、TalkBack 或 NVDA 的使用者才能完整理解結構與意義。
Screen reader(螢幕閱讀器)
Screen reader(螢幕閱讀器)是一種程式,會把內容唸出來並/或結構化螢幕內容,讓視覺受限的人能使用電腦、手機或網頁應用。
但 screen reader 並不只是「把文字唸出來」的軟體。它是一整套與介面互動的系統,會把網站或應用的視覺呈現轉換成可被理解的語音與結構化導覽。
ChatGPT、screen reader 與 WCAG AA
如果你的小工具遵循 WCAG AA 的語意標註(正確的角色、標題、按鈕標籤),它就不只讓 screen reader 能理解,也讓 ChatGPT 的語音模式更好用。使用者用聲音和 ChatGPT 對話,而模型可依相同的語意結構「虛擬地」做跟人一樣的事:找到對的介面元素、按按鈕、開連結,等等。
依據 ChatGPT Store 的要求,支援 WCAG AA 是強制性的。每個小工具與每個 tool 都要有高品質、具體的描述,並以符合 WCAG AA 的語意來編排:正確的語意、可讀的標籤、可預期的狀態。
因此,WCAG AA 不是一個「只給特別需求者的額外功能」,而是基本的設計原則,好讓 ChatGPT Apps 能完整地與你的應用互動——包含使用者以語音模式交流時。
關於 voice-UX(語音體驗)、語音對話與文字對話的差異,以及 ChatGPT Store 的要求,我們會在本模組的其他課與 App 發佈模組再談。但一切都建立在你剛看到的基礎上:語音模式 = 多模態 + 無障礙(WCAG AA + 螢幕閱讀器)。
9. ChatGPT App 視覺設計常見錯誤
錯誤 №1:硬編碼白/黑背景與文字顏色。
開發者把背景畫成白、文字畫成黑,完全沒考慮深色主題。淺色主題或許還行,深色主題就像探照燈,整個 UX 會壞掉。更好的做法是使用系統色與宿主主題(CSS 變數、prefers-color-scheme 或 Apps SDK 的 API),而自家顏色只用於重點。
錯誤 №2:過度激進的品牌化。
出現鮮豔漸層背景、自訂字體、花俏邊框。小工具開始看起來像促銷橫幅,而不是 ChatGPT 介面的一部分。指引剛好相反:極簡、「原生」的外觀,只在關鍵元素(例如主要按鈕)中使用克制的品牌色。
錯誤 №3:沒有字體排印的層級。
所有文字同一大小與字重,或反過來——在小卡片上塞三個層級的標題,還全大寫。使用者搞不清什麼是重點:名稱、價格,還是描述。更好的方式是事先約定 3–4 個層級,並一致使用:標題、關鍵參數、主體文字、附註。
錯誤 №4:元素擠在一起,沒有間距。
卡片彼此緊貼、文字緊挨邊界、按鈕緊靠文字。桌機上或許還能忍,手機上就變成雜訊。建議使用統一的間距刻度(例如 Tailwind 的 p-4、gap-3),別省空氣。
錯誤 №5:在 inline 模式硬塞 4–5 欄。
開發者腦中還停留在電商頁面,於是在聊天中擺出四欄狹長卡片。寬螢幕上也許勉強,手機上根本不可讀,還多了水平捲動。inline 小工具通常一到兩欄就夠;第三欄留給 fullscreen 吧。
錯誤 №6:忽略高度限制與 safe-area。
小工具畫出巨大的清單,沒有內部捲動,也不理會 maxHeight,結果按鈕「沉到螢幕底下」。或元素被行動裝置的缺口遮住。應該使用最大高度與安全區域的資訊,妥善分配內部高度與間距。
錯誤 №7:inline 與 fullscreen 的按鈕與卡片樣式不一致。
inline 的按鈕是綠色且圓角,fullscreen 的按鈕卻是藍色且直角。使用者會失去「是一個產品」的感覺。應該把按鈕與卡片的基礎樣式抽到共同的元件/主題中,在所有模式都重用。
錯誤 №8:「作者風」字體與裝飾花招。
載入沉重的 webfont「為了更美」,破壞了與 ChatGPT 的視覺一致性,有時還拖累效能。平台建議使用系統字體與俐落的字體排印。如果真的想展現設計品味——不如在圖示與 microcopy 上用功,而不是發動字體革命。
GO TO FULL VERSION