Figma 穿梭框与双向列表选择系统(Transfer & Dual Listbox System)组件架构实战:从双列 Auto Layout 弹性分配、全选/反选计数状态机、Variables 跨列表数据穿梭原型到 Dev Mode 前端代码交付
在企业级 ERP、SaaS 权限中台、数据仓库指标配置、用户角色指派以及复杂业务流水线编排中,穿梭框(Transfer / Dual Listbox) 是承载“大量候选数据向目标集合进行多选、转移与排序”的核心重型交互控件。相比常规的下拉多选框(Multi-Select),穿梭框提供了更加直观的双栏对照视图,允许用户在海量候选项与已选项之间自由对比、批量移动并清晰感知当下的集合边界。
然而,在许多团队的 Figma 设计系统资产中,穿梭框组件往往存在诸多工程痛点:
- 容器拉伸变形与尺寸塌陷:双栏面板未遵循弹性 Auto Layout 自动布局 规范,一旦将组件放入弹窗或宽屏栅格,中央操作按钮发生偏斜,列表项文本撑破容器或出现异常留白;
- 列表滚动与溢出裁剪混淆:未正确设置固定高度与垂直内滚动(Scroll Vertical),导致长列表项在画布中无限纵向延展,甚至与下层卡片内容产生重叠穿透;
- 计数联动与状态机断裂:全选框(Select All)、半选态(Indeterminate)、单项勾选以及底部已选计数器(如
已选 3/10 项)缺乏原子级组件逻辑,难以表现穿梭后的数据联动; - 原型演示难以模拟真实穿梭:传统方式需要针对“选中 -> 穿梭 -> 取消”绘制多达数十个画板,极耗画布渲染性能;
- 前端研发交付鸿沟:开发在 Dev Mode 开发模式 中看到的仅仅是重复的静态列表项,缺少底层对应 Ant Design 或 Headless UI 的数据驱动属性(如
dataSource、targetKeys、selectedKeys)及 WAI-ARIA 无障碍键盘焦点流转规范。
本文将深入剖析穿梭框系统的物理拓扑解耦、列表项原子状态机、Auto Layout 双栏弹性约束、Variables 跨栏穿梭原型到前端工程代码交付的全生命周期。
穿梭框的物理拓扑模型与模块解耦 #
一个工业级的穿梭框由源数据列表(Source Panel)、中央操作区(Operation Controls)与目标数据列表(Target Panel)三大骨架构成。
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Transfer System (穿梭框主组件) │
│ │
│ ┌──────────────────────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 源数据面板 (Source List) │ │ 操作按钮区 │ │ 目标面板 (Target) │ │
│ │ ──────────────────────────── │ │ ─────────── │ │ ─────────────────── │ │
│ │ [☑] 待选列表 (共 12 项) │ │ │ │ [☐] 已选列表 (3 项) │ │
│ │ [ 🔍 搜索候选人员... ] │ │ [ > ] 穿梭 │ │ [ 🔍 搜索已选... ] │ │
│ │ ──────────────────────────── │ │ │ │ ─────────────────── │ │
│ │ [☑] 01 张三 (财务部) │ │ [ < ] 移出 │ │ [☐] 04 钱七 (研发) │ │
│ │ [☑] 02 李四 (风控部) │ │ │ │ [☐] 05 孙八 (设计) │ │
│ │ [☐] 03 王五 (运维部) │ │ [ >>] 全转 │ │ [☐] 06 周九 (测试) │ │
│ │ [☐] ... (滚动列表项) │ │ │ │ │ │
│ │ ──────────────────────────── │ │ [ <<] 全清 │ │ ─────────────────── │ │
│ │ 底部操作区: 已选 2 项 / 清空 │ │ │ │ 底部操作区: 共 3 项 │ │
│ └──────────────────────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
1. 核心子模块的职责矩阵 #
| 子模块 (Sub-component) | 界面定位 | 包含的核心元素与交互能力 | 弹性与布局规范 |
|---|---|---|---|
| Header Section (头部统计栏) | 左右列表面板顶部 | 全选复选框(支持未选/半选/全选三态)、列表标题、总项数徽标 | 高度固定 40px,水平 Auto Layout,两端对齐 |
| Search Filter (搜索过滤区) | 头部下方(可选插槽) | 搜索图标、占位输入框、一键清空按钮,支持实时关键字过滤 | 宽度 Fill container,外边距 8px 12px |
| List Body (滚动列表区) | 面板主体承载区 | 纵向堆叠的列表项(Transfer Items),超出最大高度后截断 | 设为固定高度或最大高度(如 280px),开启 Clip content |
| Action Center (中央控制区) | 左右面板正中间 | 向右穿梭(>)、向左移出(<)、批量全转(>> / <<)操作按钮 | 垂直 Auto Layout,居中对齐,与左右面板保持 16px 间距 |
| Footer Section (底部操作栏) | 面板最底部(可选) | 刷新按钮、逆向选择、已勾选数与总数联动提示(如 2 / 12 items) | 高度固定 36px,顶部 1px 细分割线,内边距 8px 12px |
列表项(Transfer Item)原子级变体与状态机 #
列表项是穿梭框的最小交互原子。其必须承载文字截断、选中状态、悬停反馈与禁用边界。
1. 列表项的五大核心状态 #
| 状态 (State) | 视觉样式表现 | 交互语义与前置条件 |
|---|---|---|
| Default (常规待选) | 白底,文字 #1E293B,复选框空心无勾选 | 项目在列表中静止展示,可被勾选 |
| Hover (悬停聚焦) | 浅灰背景(#F8FAFC),复选框边框高亮为品牌色(#4F46E5) | 鼠标划过,给予视觉聚焦暗示 |
| Checked (已勾选态) | 浅蓝强调色背景(#EEF2FF),复选框实心打勾,文字清晰加粗 | 用户已圈定该项,等待点击中央穿梭按钮执行移动 |
| Disabled (置灰禁用) | 文字弱化(#94A3B8),复选框锁定灰色,指针呈现 not-allowed | 锁定项(如超级管理员账号),禁止移出或移入 |
| Active / Pressed (按下态) | 背景色加深(#E0E7FF),产生微弱下压动效 | 鼠标真实按压瞬间反馈 |
2. 文本溢出与右侧插槽防撑破技巧 #
在真实业务场景中,列表项往往包含长姓名、邮箱或所属部门(如 张三 (基础架构与技术中台-资深架构师) zhangsan@corp.com):
- 文本图层设置:列表项内部的主文本图层必须设置为
Fill container,并开启 Figma 文本属性中的 Truncate text(多余文本显示省略号); - 右侧辅助插槽(Slot):若需展示状态徽标或分类标签(Tag),应将其封装在独立 Auto Layout 容器中并锁定固定或自适应宽度,确保文本截断在标签左侧生效,绝不能将右侧标签挤出列表容器。
Auto Layout 双栏弹性约束与列表滚动规范 #
在搭建穿梭框整体组件时,很多设计师容易把宽度写死,导致组件在嵌入响应式弹窗时出现横向穿透。规范的搭建流程如下:
Transfer Root Container (Auto Layout 水平排列, Gap: 16px, Align: Center)
│
├── Left Panel: Source (Auto Layout 垂直, Fill Container 或 Fixed 240px, Min-Width: 200px)
│ ├── Header (Fixed Height 40px, Fill Container)
│ ├── Search Box (Fill Container)
│ ├── List Body (Fill Container, Height: 280px, Vertical Scroll, Clip content 勾选)
│ │ └── Stack of Items (Fill Container, Hug Contents)
│ └── Footer (Fixed Height 36px, Fill Container)
│
├── Operations Column (Auto Layout 垂直, Hug Contents, Width: Fixed 40px, Gap: 8px)
│ ├── Button Right [ > ] (Fixed 32×32px, 状态: Disabled / Enabled)
│ └── Button Left [ < ] (Fixed 32×32px, 状态: Disabled / Enabled)
│
└── Right Panel: Target (与 Left Panel 完全同构对称)
1. 约束与滚动配置的关键排障要点 #
- Clip content(裁剪内容)的正确使用:
- 列表项的父级容器
List Body必须勾选Clip content,防止列表项过多时向下方破框而出; - 在原型模式下,将
List Body的滚动行为(Overflow)设置为 Scroll vertically(垂直滚动),这样在展示 20+ 条数据时,用户可以在有限的 280px 视口内平滑上下滚动浏览;
- 列表项的父级容器
- 中央按钮的响应式垂直居中:
- 中央操作列的外层 Auto Layout 必须配置为居中对齐(Align: Center),确保无论左右列表面板是否开启搜索栏或底部操作栏,穿梭箭头始终对齐在组件的正中心;
- 按钮状态机与禁选联动:
- 向右箭头按钮
[ > ]:当左侧源列表中有任意项目被勾选(left_checked_count > 0)时,变体自动切换为Enabled(高亮可用);否则保持Disabled灰色置灰; - 向左箭头按钮
[ < ]:同理,只有当右侧目标列表中有项目被勾选时才被激活。
- 向右箭头按钮
Variables 驱动的高保真穿梭与计数原型实战 #
传统原型制作穿梭框时,每次点击穿梭都要跳转画板。借助 Figma 现代变量(Variables)与条件逻辑,可以在单个画板上实现高度拟真的跨栏穿梭效果。
1. 定义核心数字与布尔变量 #
在 Local Variables 面板中建立 Transfer_System 集合:
| 变量名称 (Variable Name) | 数据类型 (Type) | 初始值 (Default) | 业务逻辑含义 |
|---|---|---|---|
left_total_count | Number | 10 | 左侧源列表总候选项数 |
left_selected_count | Number | 0 | 左侧当前已勾选等待穿梭的项数 |
right_total_count | Number | 3 | 右侧目标列表已存在项数 |
right_selected_count | Number | 0 | 右侧当前勾选等待退回的项数 |
item_01_visible_in_left | Boolean | true | 项目 01 是否停留在左侧列表 |
item_01_checked | Boolean | false | 项目 01 是否处于勾选高亮态 |
is_transfer_right_active | Boolean | false | 向右穿梭按钮是否处于激活状态 |
2. 交互动作流配置 #
- 用户勾选左侧“项目 01”(On Click Item 01):
if item_01_checked == false { Set variable item_01_checked = true; Set variable left_selected_count = left_selected_count + 1; Set variable is_transfer_right_active = true; } else { Set variable item_01_checked = false; Set variable left_selected_count = left_selected_count - 1; if left_selected_count == 0 { Set variable is_transfer_right_active = false; } } - 用户点击中央向右穿梭按钮
[ > ](On Click Button Right):if is_transfer_right_active == true { Set variable item_01_visible_in_left = false; Set variable item_01_checked = false; Set variable left_total_count = left_total_count - 1; Set variable left_selected_count = 0; Set variable right_total_count = right_total_count + 1; Set variable is_transfer_right_active = false; } - 文本与显隐属性绑定:
- 头部计数文本通过文本属性与变量表达式绑定,实时渲染为
selected_count + " / " + total_count + " 项"; - 项目 01 图层的显隐(Layer Visibility)直接绑定
item_01_visible_in_left变量; - 按钮组件的变体直接由
is_transfer_right_active状态驱动。
- 头部计数文本通过文本属性与变量表达式绑定,实时渲染为
这种变量驱动方式不仅让原型演示完全还原真实系统,还彻底避免了多画板冗余,极大提升了文件加载性能。
Dev Mode 生产级代码交付与前端工程对齐 #
在 Dev Mode 开发模式 中,交付穿梭框必须向研发团队提供清晰的数据项结构与状态管理模型,严禁单纯交付切图。
1. 组件属性与现代前端框架(React / Ant Design)无损映射矩阵 #
| Figma 设计系统属性 | Ant Design (<Transfer />) | Headless UI / 原生双列表 | 业务工程语义 |
|---|---|---|---|
Left List Items | dataSource 数组 | availableItems 状态数组 | 候选集全量数据源 |
Right List Items | targetKeys (受控字符串数组) | selectedItems 状态数组 | 已经被移入目标列表的主键 ID 集合 |
Checked Items | selectedKeys 数组 | 选中高亮索引集合 | 当前正在被复选框勾选的高亮项 |
Show Search (Boolean) | showSearch={true} | 自定义 Search Filter 插槽 | 是否开启关键字搜索过滤栏 |
Disabled Item | record.disabled = true | <option disabled> 或 aria-disabled | 单项锁定不可移入/移出 |
Titles | titles={['待选角色', '已分配角色']} | 两个面板的 <h3 id="..."> | 左右面板顶部标题文案 |
2. 标准生产级 React + Ant Design 交付代码示例 #
import React, { useState } from 'react';
import { Transfer, Card, Tag } from 'antd';
import type { TransferDirection, TransferItem } from 'antd/es/transfer';
interface UserRecord extends TransferItem {
key: string;
title: string;
description: string;
department: string;
disabled?: boolean;
}
const mockData: UserRecord[] = [
{ key: '1', title: '张三', description: 'zhangsan@corp.com', department: '财务部' },
{ key: '2', title: '李四', description: 'lisi@corp.com', department: '风控部' },
{ key: '3', title: '王五', description: 'wangwu@corp.com', department: '运维部' },
{ key: '4', title: '赵六', description: 'zhaoliu@corp.com', department: '架构部', disabled: true },
];
export const EnterpriseRoleTransfer: React.FC = () => {
const [targetKeys, setTargetKeys] = useState<string[]>(['1']);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
const handleChange = (newTargetKeys: string[], direction: TransferDirection, moveKeys: string[]) => {
setTargetKeys(newTargetKeys);
console.log(`向 ${direction} 移动了: `, moveKeys);
};
const handleSelectChange = (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => {
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
};
return (
<Card title="角色与权限分配 (Transfer System)" className="shadow-sm max-w-4xl">
<Transfer
dataSource={mockData}
titles={['可分配人员', '已分配人员']}
targetKeys={targetKeys}
selectedKeys={selectedKeys}
onChange={handleChange}
onSelectChange={handleSelectChange}
render={(item) => (
<span className="flex items-center justify-between w-full pr-2">
<span>{item.title} ({item.description})</span>
<Tag color="blue">{item.department}</Tag>
</span>
)}
showSearch
filterOption={(inputValue, item) =>
item.title.indexOf(inputValue) !== -1 || item.department.indexOf(inputValue) !== -1
}
listStyle={{
width: 280,
height: 320,
}}
operations={['穿梭入库', '移出列表']}
/>
</Card>
);
};
3. WAI-ARIA 无障碍键盘焦点与辅助技术支持 #
- Dual Listbox 角色声明:
- 左右两栏列表容器必须赋予
role="listbox"并声明aria-multiselectable="true"; - 列表项赋予
role="option",当前勾选状态输出aria-selected="true" / "false";
- 左右两栏列表容器必须赋予
- 全键盘可操作性(Keyboard Accessibility):
- 用户通过
Tab键在左列表、穿梭按钮区、右列表之间平滑移动; - 在列表内部,使用方向键
↑/↓浏览项目,按Space(空格键)或Enter快速切换勾选状态; - 支持快捷键
Ctrl + A(Mac 上为Cmd + A)快速触发全选当前列表。
- 用户通过
企业级穿梭框落地检查清单 #
在将穿梭框正式发布至团队设计系统之前,建议对照以下检查项逐一排查:
| 校验维度 | 工业级验收规范与避坑要点 | 状态 |
|---|---|---|
| 双栏布局 | 面板采用 Auto Layout,水平宽度自适应(Min-Width: 200px),避免容器拉伸时按钮错位 | [x] 已通过 |
| 滚动裁剪 | 列表内容区域设定固定高度(280px~360px)并开启 Clip content,内部允许垂直滚动 | [x] 已通过 |
| 文本防撑破 | 列表项内部主文案开启 Truncate text 省略号截断,右侧标签锁定宽度不被挤压 | [x] 已通过 |
| 状态完备 | 具备常规态、悬停态、选中态、禁用态以及全选/半选/清空多态流转 | [x] 已通过 |
| 按钮联动 | 中央穿梭箭头严格根据左右勾选数量动态激活或置灰,防无意义空操作 | [x] 已通过 |
| 代码交付 | Dev Mode 中输出完整的 targetKeys / selectedKeys 状态映射及 WAI-ARIA 属性 | [x] 已通过 |
通过以上规范化的模块解耦、变量逻辑绑定与 Dev Mode 深度交付,团队可以快速构建出高可用、易扩展的穿梭框组件,不仅在复杂中后台系统的原型评审中游刃有余,更能极大赋能前端研发团队的无损还原。若需查阅更多复杂表单控件的排障经验,请参阅站内指南 设计系统常见故障排查手册 与 Tokens 设计令牌体系规范。
核心手册与速查工具
高频手册与实用设计生产力工具直达常见问题与答疑 (FAQ)
高频疑难解析与避坑指南完全不需要。绝大多数核心功能在免费 Starter 计划中均可完整体验与实操练习,无需额外付费购买付费团队版。