政府与公共服务 · 新资源

B.C. 设计系统.

使用真实的语义令牌、原生 React Aria 控件、条件性联系字段和完整的验证行为,构建一个 B.C. 风格的提醒偏好表单。

OpenDesign 指南6 个章节36 个参考条目原始应用指南
01

B.C. 设计系统 界面预览

在电子邮件和电话之间切换并保留输入,然后验证并查看本地联系方式偏好。

选择并查看联系方式偏好打开完整示例
原生 @bcgov/design-system-react-components 0.8.1、设计令牌 5.0.0 和 BC Sans 2.1.2。· 实现参考
02

B.C. 设计系统 设计参考

原生 @bcgov/design-system-react-components 0.8.1、设计令牌 5.0.0 和 BC Sans 2.1.2。

界面表面与文本角色

来源
名称 / 规则值 / 配置用途
--surface-color-primary-button-default#013366 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-primary-button-hover#1e5189 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-primary-button-disabled#edebe9 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-background-white#ffffff 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-background-light-gray#faf9f8 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-background-light-blue#f1f8fe 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-border-default#d8d8d8 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-border-medium#898785 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--surface-color-border-active#2e5dd7 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-color-primary#2d2d2d 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-color-secondary#474543 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-color-link#255a90 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-color-danger#ce3e39 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-color-primary-invert#ffffff 已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。

布局节奏

来源
名称 / 规则值 / 配置用途
--layout-padding-hair0.125rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-padding-xsmall0.25rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-padding-small0.5rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-padding-medium1rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-padding-large1.5rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-padding-xlarge2rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-margin-medium1rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-margin-large1.5rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-margin-xlarge2rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-margin-xxxlarge3rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-border-radius-small2px已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-border-radius-medium4px已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--layout-border-radius-large6px已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。

字体阶梯

来源
名称 / 规则值 / 配置用途
--typography-font-size-small-body0.875rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-body1rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-large-body1.125rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-h51.25rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-h41.5rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-h31.75rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-h22rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-h12.25rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
--typography-font-size-display3rem已发布的 B.C. 角色;应按用途使用,而不是替换成原始颜色或尺寸值。
03

B.C. 设计系统 完整指南

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

DESIGN-british-columbia.md

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

下载指南

B.C. 设计系统 当前 React 库与设计令牌

选择受维护的实现

当前的 B.C. Design System 提供基于 React Aria 构建的组件。此示例固定使用 react-components 0.8.1 及其依赖 design-tokens 5.0.0。较早的 B.C. Web Components 仓库是独立的旧版实现;导入它并不能复现当前的 React 库。复制示例前,请先确认实现与文档相符。

摘自本 OpenDesign 指南

导入实际字体

这些组件使用带空格的 BC Sans 字体系列名称。2.1.2 版本提供 css/BC_Sans.css;旧版 BCSans.css 条目已标记为弃用,且声明了不同的字体系列名称。如果开发人员的电脑上已安装 BC Sans,可能会掩盖字体未导入的问题。此示例内置了已发布的字体,因此显示效果不依赖本地安装。

B.C. 设计系统语义颜色与间距角色

先看角色,再选择对应值

主按钮使用深蓝色主按钮填充色,辅助内容使用 typography-color-secondary,错误消息使用 typography-color-danger。表格列出了当前 CSS 的导出名称;即使未来主题更改底层值,名称表达的用途仍保持稳定。请勿将非活动控件颜色与普通辅助文本颜色混淆。

有意识地规划布局尺寸

表单字段之间相距 24px,主要任务与辅助说明之间则留出更宽的 48px 间距。这些数值与库的间距节奏相符,而整体双栏布局是为此示例单独设计的。在移动端,布局会合并为单栏,同时保留表单标签的字号,也不会将说明文字变成工具提示。

B.C. 设计系统 条件字段与保留值

仅验证所选的联系方式

原生 RadioGroup 用于控制显示“电子邮件地址”还是“电话号码”TextField。切换方式时,两个值都会保留在 React 状态中,但只有当前选中的字段会参与审核校验。这样既能保留有用的输入,也能避免不可见字段阻碍当前任务。

提供清晰易懂的本地错误提示

电子邮件字段会检查地址格式是否基本正确,电话号码示例则要求至少包含十位数字。这些明确只是示例规则,并非完整的国际联系方式验证。提交审核时,如果输入无效,会设置 isInvalid 并提供针对该字段的 errorMessage。未勾选同意框时会显示消息;焦点会移至第一个无效控件。

B.C. 设计系统 React Aria 组件约定

使用受控值和文档规定的处理器

TextField 接收 value 和 onChange;RadioGroup 接收 value 和 onChange;Checkbox 使用 isSelected 和 onChange。这些处理器接收文档规定的值,而不是必须通过 event.target 读取的浏览器事件对象。Button 使用 type=submit,并依靠原生表单的提交事件,使键盘提交也遵循相同的验证流程。

让审核反馈保持同步

B.C. 设计系统 无障碍名称与响应式布局

确保标签、说明和错误提示彼此关联

原生组件提供 React Aria 对标签、帮助文本和错误消息的关联支持。应用仍须提供有意义的标签、清晰的分组说明和易于理解的验证文本。测试时应点击可见的单选项标签并使用键盘操作;内部视觉隐藏的输入框不是可见的交互目标。

将辅助说明与表单分开

旁边的说明框解释了本地操作流程,并使用原生 details 展示实现背景。在窄屏布局中,它会按阅读顺序排在表单之后。信息图标仅作装饰,含义由标题表达。研究这些组件无需使用 B.C. 官方标志,也不必声称这是政府服务。

B.C. 设计系统 完整的 React 实现

复现经验证的源代码

创建 Vite React 项目,并安装固定版本的组件、令牌和字体包。使用提供的 main.jsx 和 bc.css,并添加根挂载元素。此代码包包含原生单选框、字段、复选框和按钮行为,以及完整的本地验证和状态转换。无需私有的 API 或凭据。

  1. 安装固定版本的软件包,并导入 css/BC_Sans.css。
  2. 保存 main.jsx 和 bc.css,并保留根挂载元素。
  3. 提交空表单,然后修正可见字段中的内容。
  4. 切换联系方式,并确认先前输入的值仍然保留。

让条件验证与所选联系方式保持一致

RadioGroup 和 TextField 的 onChange 处理器接收值,Checkbox 的 onChange 处理器则接收所选布尔值。直接存储这些值,不要对所有组件一概使用通用的 event.target.value 处理器。即使 React Aria 在视觉上隐藏了原生输入框,可见标签仍是交互目标。

更改联系方式时,保留两项草稿值,但清除旧的审核结果,并且只验证当前选中的方式。提交后,将焦点移至第一个可见的无效控件。这样一来,用户可以比较不同联系方式,而不会丢失电子邮件地址,也不会被隐藏的电话字段阻挡。示例中的十位数字电话规则仅适用于本地场景,应替换为服务实际要求的国际电话号码规则。

  • 仅验证所选的联系方式字段。
  • 更改任一选项都会清除旧的审核反馈。
  • 结果不会声称已安排提醒。

B.C. 设计系统组件示例

main.jsx — 完整可运行示例

选择并审核一种联系方式。以下源代码是完整的 React 入口文件,包含全部状态和验证逻辑。

jsx
import React, { useState, useRef } from "react";
import { createRoot } from "react-dom/client";
import {
  Button,
  Radio,
  RadioGroup,
  TextField,
  Checkbox,
} from "@bcgov/design-system-react-components";
import "@bcgov/bc-sans/css/BC_Sans.css";
import "./bc.css";
function App() {
  const [method, setMethod] = useState("email"),
    [email, setEmail] = useState(""),
    [phone, setPhone] = useState(""),
    [consent, setConsent] = useState(false),
    [submitted, setSubmitted] = useState(false),
    [result, setResult] = useState("");
  const panel = useRef(null);
  const invalidEmail = !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
    invalidPhone = phone.replace(/\D/g, "").length < 10;
  function change(fn, value) {
    fn(value);
    setResult("");
    setSubmitted(false);
  }
  function review(e) {
    e.preventDefault();
    setSubmitted(true);
    if ((method === "email" ? invalidEmail : invalidPhone) || !consent) {
      requestAnimationFrame(() =>
        panel.current
          .querySelector(
            '[aria-invalid="true"] input,input[aria-invalid="true"],input[type=checkbox]:not(:checked)',
          )
          ?.focus(),
      );
      return;
    }
    setResult(
      `Local preference ready: ${method === "email" ? "Email to " + email : "Phone call to " + phone}. No reminder has been scheduled.`,
    );
  }
  return (
    <main>
      <header>
        <div className="eyebrow">
          SERVICE REMINDER / ORIGINAL APPLICATION STUDY
        </div>
        <h1>Choose how to hear from us</h1>
        <p>
          Set a sample reminder preference. Use fictional contact details in
          this local example.
        </p>
      </header>
      <div className="columns">
        <form onSubmit={review} noValidate ref={panel}>
          <RadioGroup
            label="Contact method"
            value={method}
            onChange={(v) => change(setMethod, v)}
            description="Choose one way to receive a reminder."
          >
            <Radio value="email">Email</Radio>
            <Radio value="phone">Phone call</Radio>
          </RadioGroup>
          <div className="field">
            {method === "email" ? (
              <TextField
                label="Email address"
                type="email"
                value={email}
                onChange={(v) => change(setEmail, v)}
                isRequired
                isInvalid={submitted && invalidEmail}
                errorMessage="Enter an email address in the format [email protected]."
                description="Use a sample address, such as [email protected]."
              />
            ) : (
              <TextField
                label="Phone number"
                type="tel"
                value={phone}
                onChange={(v) => change(setPhone, v)}
                isRequired
                isInvalid={submitted && invalidPhone}
                errorMessage="Enter a phone number with at least 10 digits."
                description="Sample validation only; a real service needs a jurisdiction-aware phone rule."
              />
            )}
          </div>
          <div className="field">
            <Checkbox
              isSelected={consent}
              onChange={(v) => change(setConsent, v)}
              isInvalid={submitted && !consent}
            >
              I agree to use these details for this sample reminder
            </Checkbox>
            {submitted && !consent && (
              <p className="error" role="alert">
                Confirm the reminder preference before continuing.
              </p>
            )}
          </div>
          <Button type="submit" variant="primary">
            Review preference
          </Button>
          <p className="result" role="status">
            {result}
          </p>
        </form>
        <aside>
          <div className="status-mark" aria-hidden="true">
            i
          </div>
          <h2>What happens next</h2>
          <ol>
            <li>Choose a contact method.</li>
            <li>Review the sample details.</li>
            <li>Connect a real notification service before scheduling.</li>
          </ol>
          <p>
            This example keeps both fields while switching methods, but only
            reviews the selected method.
          </p>
          <details>
            <summary>About this specimen</summary>
            <p>
              Native B.C. React Aria components with BC Sans. The form is
              original and is not an official government service.
            </p>
          </details>
        </aside>
      </div>
    </main>
  );
}
createRoot(document.getElementById("root")).render(<App />);

代码参考

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

bc.css — 原始响应式布局

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

css
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  font:
    16px/1.5 "BC Sans",
    sans-serif;
  color: var(--typography-color-primary, #2d2d2d);
  background: white;
}
main {
  padding: 32px;
}
header {
  border-top: 6px solid #fcba19;
  border-bottom: 1px solid #d8d8d8;
  padding: 24px 0;
  margin-bottom: 32px;
}
.eyebrow {
  font-size: 13px;
  color: #474543;
}
h1 {
  font-size: 32px;
  line-height: 1.25;
  margin: 8px 0 16px;
}
h2 {
  font-size: 24px;
  margin: 0 0 16px;
}
p {
  margin: 8px 0 16px;
}
.columns {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 48px;
}
form {
  max-width: 620px;
}
.field {
  margin: 24px 0;
}
aside {
  padding: 24px;
  background: #f1f8fe;
  border-left: 4px solid #1a5a96;
  align-self: start;
}
.status-mark {
  background: #1a5a96;
  color: white;
  border-radius: 50%;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  font-weight: bold;
  margin-bottom: 16px;
}
ol {
  padding-left: 24px;
}
li {
  padding-left: 4px;
  margin: 12px 0;
}
.error {
  color: #ce3e39;
  font-size: 14px;
}
.result {
  margin-top: 24px;
  color: #24613b;
}
summary {
  cursor: pointer;
  text-decoration: underline;
  color: #1a5a96;
}
details {
  margin-top: 24px;
}
details p {
  font-size: 14px;
}
@media (max-width: 767px) {
  main {
    padding: 16px;
  }
  .columns {
    grid-template-columns: 1fr;
    gap: 24px;
  }
  h1 {
    font-size: 28px;
  }
  aside {
    padding: 16px;
  }
}

代码参考

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

B.C. 设计系统 Agent 设置

实现起点

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

sh
npm create vite@latest bc-reminder -- --template react
cd bc-reminder
npm install
npm install @bcgov/[email protected] @bcgov/[email protected] @bcgov/[email protected]
# Replace src/main.jsx and add src/bc.css using the complete examples.
npm run dev

适用于你项目的提示词

Agent 提示词
使用 B.C. React components 0.8.1 和 BC Sans 2.1.2 构建提醒表单。保留条件验证、已保留的输入内容、可见错误以及过期结果清除功能。使用当前设计令牌名称和正确的带空格字体系列。尚无正式通知约定前,请将示例保留在本地。
05

B.C. 设计系统 来源与范围

基于当前公开的 B.C. React 库进行的 OpenDesign 应用研究。这是原创的本地表单,并非官方政府服务;请使用虚构的联系信息。