# Kiwi Orbit 设计系统

## Kiwi Orbit 设计系统界面预览

更改旅客人数和附加项目，查看费用明细；编辑选项后，清除过时的审核结果。

## Kiwi Orbit 设计系统设计参考

Native Orbit React 组件 27.7.0，搭配 defaultTheme 和编译后的 Tailwind CSS；令牌参考版本 11.0.0。

### 产品操作色彩

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| buttonPrimaryBackground | #008F7B | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
| buttonPrimaryBackgroundHover | #007565 | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
| buttonPrimaryBackgroundActive | #006153 | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
| buttonPrimaryForeground | #FFFFFF | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
| buttonPrimaryForegroundHover | #FFFFFF | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
| buttonPrimaryForegroundActive | #FFFFFF | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
| buttonPrimaryBorderColorFocus | rgba(0, 143, 123, 0.5) | 主要操作状态参考。悬停、激活和焦点状态应各自承担不同作用。 |
### 组件间距

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| space50 | 2px | 已发布令牌包中的实际像素间距档位。 |
| space100 | 4px | 已发布令牌包中的实际像素间距档位。 |
| space150 | 6px | 已发布令牌包中的实际像素间距档位。 |
| space200 | 8px | 已发布令牌包中的实际像素间距档位。 |
| space300 | 12px | 已发布令牌包中的实际像素间距档位。 |
| space400 | 16px | 已发布令牌包中的实际像素间距档位。 |
| space500 | 20px | 已发布令牌包中的实际像素间距档位。 |
| space600 | 24px | 已发布令牌包中的实际像素间距档位。 |
| space800 | 32px | 已发布令牌包中的实际像素间距档位。 |
| space1000 | 40px | 已发布令牌包中的实际像素间距档位。 |
| space1200 | 48px | 已发布令牌包中的实际像素间距档位。 |
### 圆角半径

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| borderRadius50 | 2px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadius100 | 4px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadius150 | 6px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadius200 | 8px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadius300 | 12px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadius400 | 16px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadiusNone | 0 | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
| borderRadiusFull | 9999px | 不同的圆角半径适用于控件和较大的旅行信息界面表面。 |
### 正文文字样式

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| fontSizeTextSmall | 13px | 已发布的正文样式；自定义页面标题由应用组合实现。 |
| fontSizeTextNormal | 15px | 已发布的正文样式；自定义页面标题由应用组合实现。 |
| fontSizeTextLarge | 16px | 已发布的正文样式；自定义页面标题由应用组合实现。 |
| fontSizeTextExtraLarge | 18px | 已发布的正文样式；自定义页面标题由应用组合实现。 |
### 产品色与中性色界面表面

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| paletteCloudLight | #F5F7F9 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteCloudNormal | #E8EDF1 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteInkDark | #252A31 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteInkLight | #657890 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteInkNormal | #4F5E71 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteProductLight | #E1F4F3 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteProductNormal | #008F7B | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
| paletteProductDark | #007061 | 使用平和的界面表面和清晰易读的文字，衬托产品操作色。 |
## Kiwi Orbit 设计系统完整指南

实用指南，介绍如何为真实界面选择、应用和评审此系统。

完整指导、实现起点和源代码链接。

## Kiwi Orbit 设计系统行程选择布局

### 先展示行程，再展示附加选项

> 行程摘要会在行李选项之前显示两个机场代码、城市名称和虚构的行程时长。这样用户比较附加选项时，购买背景信息始终清晰可见。虚线连接线是原创的 CSS 插图，并非航空公司的航线图，也不代表真实服务承诺。

### 将选项与总价分开展示

旅客人数和行李选项放在一侧，价格明细则显示在旁边。所选控件和金额均由同一状态生成。在手机上，摘要会按文档顺序显示在选项之后，让用户清楚了解哪些输入决定了总价。

## Kiwi Orbit 设计系统产品配色与反馈

### 主要操作使用产品色系

令牌包提供绿色产品操作色，并为悬停和激活状态设置了不同色阶。参考内容列出了按钮对应的精确令牌值，而非依赖臆测的品牌绿。云雾色中性背景衬托可选附加项，同时避免每个面板都与查看操作争夺注意力。

### 让反馈与结果相对应

原生成功提示会显示“已在本地查看选项”，并列出旅客人数和总价。每当选项发生变化，提示就会清除。因此，之前的绿色确认不会继续显示，造成新编辑的附加选项也已通过验证的错觉。

## Kiwi Orbit 设计系统排版与价格层级

### 使用原生文字样式展示说明文案

Orbit 提供 13px 小号和 15px 常规正文样式，以及更大的字号选项。示例使用原生 Text 和 Heading 组件，行程代码和总价则采用自定义强调样式。此处浏览器使用文档中说明的备用字体栈；不会暗中加载 Kiwi.com 品牌字体。

### 将单位与金额放在一起

每项行李价格都注明“每位旅客”。价格明细会将所选附加项的单价乘以旅客人数，总价的视觉权重也高于各项明细。在移动设备上仍应保留计价单位，因为醒目却未说明适用范围的大数字很容易被误读。

## Kiwi Orbit 设计系统圆角与间距比例

### 区分控件与较大的界面表面

当前圆角半径比例包含 2、4、6、8、12 和 16px 档位，以及 none 和 full。自定义行程面板和摘要面板使用 12px 圆角，原生控件则保留各自的主题规则。如果所有嵌套元素都使用最大圆角，表单就会失去层次感。

### 换行时保持选项行完整

每个行李选项行都将原生复选框、额度说明和价格组合在一起。描述内容可灵活伸缩，价格则保持清晰可读。在任一主要区域窄到无法支持比较之前，页面就会切换为单列布局。

## Kiwi Orbit 设计系统 受控复选框与选择框

### 使用组件标签和信息字段

Checkbox 通过公开属性接收可见标签和辅助说明。Select 接收标签以及从一到四位旅客的明确选项。这些原生组件组合保留了交互和标签行为，不会用可点击的 div 假装成行李控件。

### 分别建模每项附加服务

随身行李和托运行李分别使用独立的布尔值。应用不会暗示选择其中一项就会自动取消另一项。这是一个刻意简化的示例规则；真实航空公司可能提供互斥套餐，或按旅客设置行李额度，届时需要采用不同的数据模型。

## Kiwi Orbit 设计系统选择计算与查看

### 通过一个公式计算总价

示例按旅客人数乘以基础票价与已选的每人附加费用之和。它不会另外存储一个可独立编辑、可能与可见选项不一致的总价。所有价格和额度均为虚构，并非 Kiwi.com 当前提供的产品。

### 保存查看时的状态快照

查看操作会将旅客人数、行李选项和总价一并保存。更改任何控件都会清除此快照。因此，这个示例展示了状态一致性，但不会把本地点击说成预订、付款、实时余量查询或最终报价。

## Kiwi Orbit 设计系统无障碍选项反馈

### 同时检查标签与重新计算结果

键盘或指针用户都可以使用原生控件选择旅客人数，并切换任一行李选项。查看结果会显示在状态区域中。对于非必选行李，不会伪造验证要求：在这张虚构票价中，两项附加服务都不选也是有效状态。

1. 选择两位旅客并添加随身行李。

2. 添加托运行李，并核对明细中的三项金额。

3. 在本地查看选项，然后更改一项选择，确认之前的成功提示已消失。

### 避免隐藏后果

页面会在表单上方和总价旁说明示例的虚构性质。操作按钮写的是“查看本地选项”，而非“购买”或“预订”。所有更改都仅在页面内生效；对于不需要个人旅客信息的演示，也不会索取这类信息。

## Kiwi Orbit 设计系统 Provider 与编译后的样式

### 使用匹配的稳定版本

此示例固定使用 React 组件 27.7.0 和令牌引用 11.0.0。它导入 OrbitProvider、软件包的 defaultTheme，以及编译后的 lib/tailwind.css。即使 React 树能够渲染，省略该样式表也会导致依赖工具类的组件样式不完整。

- OrbitProvider 和软件包的 defaultTheme。

- 来自 components 27.7.0 的编译后 lib/tailwind.css。

- 软件包构建环境需要 Node.js 22 或更高版本。

### 明确说明部署要求

该软件包要求 Node.js 22 或更高版本。提供的 Vite 入口可在此环境下使用固定版本的组件软件包复现。原始的 CSS 仅负责行程组合；原生控件则保留已发布的组件样式和提供程序行为。

## Kiwi Orbit 设计系统组件示例

### main.jsx — 完整可运行示例

选择行李并查看示例总价。下方是完整的 React 入口代码，包含所有状态和验证逻辑。

```
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
  OrbitProvider,
  Button,
  Checkbox,
  Select,
  Alert,
  Heading,
  Text,
} from "@kiwicom/orbit-components";
import defaultTheme from "@kiwicom/orbit-components/lib/defaultTheme";
import "@kiwicom/orbit-components/lib/tailwind.css";
import "./orbit.css";
function App() {
  const [travellers, setTravellers] = useState("1"),
    [cabin, setCabin] = useState(false),
    [checked, setChecked] = useState(false),
    [saved, setSaved] = useState(null);
  const count = Number(travellers),
    extras = (cabin ? 18 : 0) + (checked ? 32 : 0),
    total = count * (96 + extras);
  function update(fn) {
    fn();
    setSaved(null);
  }
  return (
    <OrbitProvider theme={defaultTheme}>
      <main className="trip">
        <header>
          <Text size="small">ORIGINAL TRAVEL SPECIMEN</Text>
          <Heading as="h1" type="title1">Choose what travels with you</Heading>
          <Text>
            Fictional one-way fare. Prices illustrate the interface, not a
            bookable flight.
          </Text>
        </header>
        <div className="trip-grid">
          <section>
            <div className="route">
              <div>
                <strong>PRG</strong>
                <span>Prague</span>
              </div>
              <div className="route-line">
                <span>1h 45m · direct</span>
                <i></i>
              </div>
              <div>
                <strong>AMS</strong>
                <span>Amsterdam</span>
              </div>
            </div>
            <div className="options">
              <Heading as="h2" type="title3">Travellers and baggage</Heading>
              <Select
                label="Travellers"
                ariaLabel="Travellers"
                value={travellers}
                options={[1, 2, 3, 4].map((n) => ({
                  value: String(n),
                  label: n + " adult" + (n === 1 ? "" : "s"),
                }))}
                onChange={(e) => update(() => setTravellers(e.target.value))}
              />
              <div className="bag-option">
                <div>
                  <Heading as="h3" type="title4">Personal item</Heading>
                  <Text size="small">
                    Fits under the seat · included in this example fare
                  </Text>
                </div>
                <strong>Included</strong>
              </div>
              <div className="bag-option">
                <Checkbox
                  label="Add cabin baggage"
                  info="One 10 kg bag per traveller · fictional allowance"
                  checked={cabin}
                  onChange={(e) => update(() => setCabin(e.target.checked))}
                />
                <strong>
                  €18<span>/ traveller</span>
                </strong>
              </div>
              <div className="bag-option">
                <Checkbox
                  label="Add checked baggage"
                  info="One 20 kg bag per traveller · fictional allowance"
                  checked={checked}
                  onChange={(e) => update(() => setChecked(e.target.checked))}
                />
                <strong>
                  €32<span>/ traveller</span>
                </strong>
              </div>
            </div>
          </section>
          <aside>
            <Heading as="h2" type="title3">Your example selection</Heading>
            <dl>
              <dt>Base fare × {count}</dt>
              <dd>€{96 * count}</dd>
              {cabin && (
                <>
                  <dt>Cabin baggage × {count}</dt>
                  <dd>€{18 * count}</dd>
                </>
              )}
              {checked && (
                <>
                  <dt>Checked baggage × {count}</dt>
                  <dd>€{32 * count}</dd>
                </>
              )}
            </dl>
            <div className="trip-total">
              <span>Total</span>
              <strong>€{total}</strong>
            </div>
            <Text size="small">
              All amounts are illustrative. No payment or reservation will be
              created.
            </Text>
            <Button
              fullWidth
              onClick={() => setSaved({ count, cabin, checked, total })}
            >
              Review local selection
            </Button>
            {saved && (
              <div role="status" className="trip-confirm">
                <Alert type="success" title="Selection reviewed locally">
                  {saved.count} traveller{saved.count === 1 ? "" : "s"} · €
                  {saved.total}. No booking created.
                </Alert>
              </div>
            )}
          </aside>
        </div>
        <footer>
          Native Orbit 27.7.0 components and compiled package styles. Original
          layout and fictional pricing logic by OpenDesign.
        </footer>
      </main>
    </OrbitProvider>
  );
}
createRoot(document.getElementById("root")).render(<App />);
```

代码参考

- 创建 Vite React 项目，保留 <div id="root"></div>，并将以下内容保存为 src/main.jsx。

### orbit.css — 原始响应式布局

围绕原生组件构建的应用布局。

```
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  background: #f5f7f9;
  color: #252a31;
  font:
    16px/1.5 Arial,
    sans-serif;
}
.trip {
  padding: 32px;
}
.trip header {
  max-width: 760px;
  margin-bottom: 28px;
}
.trip header h1 {
  margin: 8px 0 12px;
}
.trip-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(280px, 1fr);
  gap: 24px;
}
.route,
.options,
.trip aside {
  background: #fff;
  border-radius: 12px;
  border: 1px solid #e8edf1;
  padding: 24px;
}
.route {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 20px;
}
.route strong {
  font-size: 32px;
  display: block;
}
.route span {
  font-size: 14px;
  display: block;
  color: #4f5e71;
}
.route-line {
  flex: 1;
  text-align: center;
}
.route-line i {
  display: block;
  border-top: 2px dashed #bac7d5;
  margin-top: 12px;
}
.options > h3 {
  margin-bottom: 20px;
}
.options > div:first-of-type {
  margin-bottom: 12px;
}
.bag-option {
  display: flex;
  gap: 20px;
  align-items: center;
  justify-content: space-between;
  padding: 24px 0;
  border-top: 1px solid #e8edf1;
}
.bag-option:last-child {
  padding-bottom: 0;
}
.bag-option > strong {
  white-space: nowrap;
  font-size: 18px;
}
.bag-option > strong span {
  display: block;
  font-size: 12px;
  font-weight: 400;
  color: #657890;
}
.trip aside {
  align-self: start;
}
.trip dl {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px;
  margin: 24px 0;
}
.trip dd {
  margin: 0;
  text-align: right;
  font-weight: 600;
}
.trip-total {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 1px solid #bac7d5;
  padding: 20px 0;
}
.trip-total strong {
  font-size: 32px;
}
.trip aside > button {
  margin-top: 24px;
}
.trip-confirm {
  margin-top: 20px;
}
.trip footer {
  font-size: 12px;
  color: #657890;
  margin-top: 24px;
}
@media (max-width: 900px) {
  .trip-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 550px) {
  .trip {
    padding: 16px;
  }
  .route,
  .options,
  .trip aside {
    padding: 20px;
  }
  .route {
    gap: 12px;
  }
  .route strong {
    font-size: 28px;
  }
  .route-line span {
    font-size: 12px;
  }
  .bag-option {
    align-items: flex-start;
    gap: 12px;
  }
}
```

代码参考

- 将其保存在 main.jsx 旁边。这些自定义组合规则有别于库的设计令牌参考。

## Kiwi Orbit 设计系统 Agent 设置

### 实现起点

已打包固定的原生示例，并在本地浏览器中完成检查。该示例仅使用本地应用状态。

```
npm create vite@latest orbit-trip -- --template react
cd orbit-trip
npm install
npm install react@18.3.1 react-dom@18.3.1 @kiwicom/orbit-components@27.7.0
# Use Node.js >=22. Replace src/main.jsx and add src/orbit.css.
npm run dev
```

### 适用于你项目的提示词

```
使用 Orbit 原生组件和编译后的样式，构建旅行附加服务选择器。确保路线信息、每位旅客的价格、已选服务和总价保持一致。明确标注虚构价格和本地预览；编辑时清除过期的预览内容，并避免暗示具备预订或支付功能。
```

## Kiwi Orbit 设计系统来源与范围

OpenDesign 应用指南。路线、时长、价格和行李额度均为虚构内容。Orbit 原生控件在本地运行；不会执行航班搜索、预订或支付。

- Orbit 入门指南 当前组件和令牌版本及实现资源。

- Orbit 组件源代码 原生属性、样式和软件包要求。

- 固定版本的令牌导出 精确的产品、字体、间距和圆角值。

## Kiwi Orbit 设计系统相关资源

### Maersk 设计系统

通过真实的浅色主题角色、标题与正文的字号层级、嵌套圆角和标识符处理方式，了解 Maersk 的运营界面语言。示例货运查询展示精确匹配参考编号及无障碍事件历史，但不声称能够访问实时物流数据。

### Microsoft Fluent 2

构建一个 Fluent UI React v9 工作区，使用真实语义令牌、Web 字体和间距阶梯、受控标签页，以及经过验证的访问权限对话框。比较主题值，查看组件结构，并在使用完整的 React 示例之前尝试本地交互。

### Monday Vibe

使用 Vibe 4 令牌和 React 控件，构建可运行的交付看板。了解语义操作颜色与看板标签的区别、命名间距级别、字体简写，以及新应用所需的显式 CSS 设置。


- [实现参考](https://orbit.kiwi/getting-started/)
- [来源](https://cdn.jsdelivr.net/npm/@kiwicom/orbit-design-tokens@11.0.0/dist/index.js)
- [代码参考](https://orbit.kiwi/getting-started/)
- [Orbit 入门指南 当前组件和令牌版本及实现资源。](https://orbit.kiwi/getting-started/)
- [Orbit 组件源代码 原生属性、样式和软件包要求。](https://github.com/kiwicom/orbit)
- [固定版本的令牌导出 精确的产品、字体、间距和圆角值。](https://cdn.jsdelivr.net/npm/@kiwicom/orbit-design-tokens@11.0.0/dist/index.js)
