Switch 开关
用于布尔状态的开关组件。
用法
import { Switch, SwitchGroup, Label } from '@lenso/ui';此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";
export function Basic() { return ( <Switch> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Enable notifications </Switch.Content> </Switch> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import { Switch, Label, Description } from '@lenso/ui';
export default () => ( <Switch> <Switch.Control> <Switch.Thumb> <Switch.Icon/> {/* 可选 */} </Switch.Thumb> </Switch.Control> <Switch.Content> <Label /> <Description /> {/* 可选 */} </Switch.Content> </Switch>);要对多个 Switch 进行分组,请使用 SwitchGroup 组件:
import { Switch, SwitchGroup, Label } from '@lenso/ui';
export default () => ( <SwitchGroup> <Switch> <Switch.Control> <Switch.Thumb /> </Switch.Control> <Label>Option 1</Label> </Switch> <Switch> <Switch.Control> <Switch.Thumb /> </Switch.Control> <Label>Option 2</Label> </Switch> </SwitchGroup>);示例
尺寸
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", gap: "1.5rem" } });export function Sizes() { return ( <div {...stylex.props(styles.root)}> <Switch size="sm"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Small </Switch.Content> </Switch> <Switch size="md"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Medium </Switch.Content> </Switch> <Switch size="lg"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Large </Switch.Content> </Switch> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带图标
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { BellFill, BellSlash, Check, Microphone, MicrophoneSlash, Moon, Power, Sun, VolumeFill, VolumeSlashFill,} from "@gravity-ui/icons";import { Switch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", gap: ".75rem" }, icon: { width: ".75rem", height: ".75rem", color: "inherit", opacity: 1 }, offIcon: { opacity: 0.7 }, green: { backgroundColor: { default: "rgb(34 197 94 / .8)", ":is([data-slot='switch'][data-checked] *)": "rgb(34 197 94 / .8)", ":is([data-slot='switch'][data-checked]:hover *)": "rgb(34 197 94 / .8)", ":is([data-slot='switch'][data-checked]:active *)": "rgb(34 197 94 / .8)", }, }, red: { backgroundColor: { default: "rgb(239 68 68 / .8)", ":is([data-slot='switch'][data-checked] *)": "rgb(239 68 68 / .8)", ":is([data-slot='switch'][data-checked]:hover *)": "rgb(239 68 68 / .8)", ":is([data-slot='switch'][data-checked]:active *)": "rgb(239 68 68 / .8)", }, }, purple: { backgroundColor: { default: "rgb(168 85 247 / .8)", ":is([data-slot='switch'][data-checked] *)": "rgb(168 85 247 / .8)", ":is([data-slot='switch'][data-checked]:hover *)": "rgb(168 85 247 / .8)", ":is([data-slot='switch'][data-checked]:active *)": "rgb(168 85 247 / .8)", }, }, blue: { backgroundColor: { default: "rgb(59 130 246 / .8)", ":is([data-slot='switch'][data-checked] *)": "rgb(59 130 246 / .8)", ":is([data-slot='switch'][data-checked]:hover *)": "rgb(59 130 246 / .8)", ":is([data-slot='switch'][data-checked]:active *)": "rgb(59 130 246 / .8)", }, },});const icons = { check: { off: Power, on: Check, selectedControl: styles.green }, darkMode: { off: Moon, on: Sun, selectedControl: undefined }, microphone: { off: Microphone, on: MicrophoneSlash, selectedControl: styles.red }, notification: { off: BellSlash, on: BellFill, selectedControl: styles.purple }, volume: { off: VolumeFill, on: VolumeSlashFill, selectedControl: styles.blue },};export function WithIcons() { return ( <div {...stylex.props(styles.root)}> {Object.entries(icons).map(([key, value]) => ( <Switch key={key} defaultChecked aria-label={key} size="lg" render={(props, { checked }) => ( <span {...props}> <Switch.Content> <Switch.Control xstyle={checked && value.selectedControl}> <Switch.Thumb> <Switch.Icon> {checked ? ( <value.on {...stylex.props(styles.icon)} aria-hidden="true" /> ) : ( <value.off {...stylex.props(styles.icon, styles.offIcon)} aria-hidden="true" /> )} </Switch.Icon> </Switch.Thumb> </Switch.Control> </Switch.Content> </span> )} /> ))} </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
禁用
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";export function Disabled() { return ( <Switch disabled> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Enable notifications </Switch.Content> </Switch> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
无标签
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";export function WithoutLabel() { return ( <Switch aria-label="Enable notifications"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> </Switch.Content> </Switch> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带描述
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Description, Switch, TextField } from "@lenso/ui";import { switchSupportingStyles } from "@lenso/tokens/switch";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { maxWidth: "24rem" } });export function WithDescription() { return ( <div {...stylex.props(styles.root)}> <TextField> <Switch aria-describedby="public-profile-help"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Public profile </Switch.Content> <Description id="public-profile-help" xstyle={switchSupportingStyles.direct}> Allow others to see your profile information </Description> </Switch> </TextField> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
默认选中
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";export function DefaultSelected() { return ( <Switch defaultChecked> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Enable notifications </Switch.Content> </Switch> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控组件
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", flexDirection: "column", gap: "1rem" }, status: { fontSize: ".875rem", color: "var(--muted)" },});export function Controlled() { const [isSelected, setIsSelected] = useState(false); return ( <div {...stylex.props(styles.root)}> <Switch checked={isSelected} onCheckedChange={setIsSelected}> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Enable notifications </Switch.Content> </Switch> <p {...stylex.props(styles.status)}>Switch is {isSelected ? "on" : "off"}</p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
标签位置
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", flexDirection: "column", gap: "1rem" } });export function LabelPosition() { return ( <div {...stylex.props(styles.root)}> <Switch> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Label after </Switch.Content> </Switch> <Switch> <Switch.Content> Label before <Switch.Control> <Switch.Thumb /> </Switch.Control> </Switch.Content> </Switch> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
分组
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch, SwitchGroup } from "@lenso/ui";export function Group() { return ( <SwitchGroup> <Switch name="notifications"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Allow Notifications </Switch.Content> </Switch> <Switch name="marketing"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Marketing emails </Switch.Content> </Switch> <Switch name="social"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Social media updates </Switch.Content> </Switch> </SwitchGroup> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
横向分组
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch, SwitchGroup } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { overflowX: "auto" } });export function GroupHorizontal() { return ( <SwitchGroup xstyle={styles.root} orientation="horizontal"> <Switch name="notifications"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Notifications </Switch.Content> </Switch> <Switch name="marketing"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Marketing </Switch.Content> </Switch> <Switch name="social"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Social </Switch.Content> </Switch> </SwitchGroup> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
表单集成
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Button, Switch, SwitchGroup } from "@lenso/ui";import type { FormEvent } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ form: { display: "flex", flexDirection: "column", gap: "1rem" }, submit: { marginTop: "1rem" },});export function Form() { const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = new FormData(e.currentTarget); alert( `Form submitted with:\n${Array.from(formData.entries()) .map(([key, value]) => `${key}: ${value}`) .join("\n")}`, ); }; return ( <form {...stylex.props(styles.form)} onSubmit={handleSubmit}> <SwitchGroup> <Switch name="notifications" value="on"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Enable notifications </Switch.Content> </Switch> <Switch defaultChecked name="newsletter" value="on"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Subscribe to newsletter </Switch.Content> </Switch> <Switch name="marketing" value="on"> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Receive marketing updates </Switch.Content> </Switch> </SwitchGroup> <Button xstyle={styles.submit} size="sm" type="submit" variant="primary"> Submit </Button> </form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
渲染属性
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";export function RenderProps() { return ( <Switch render={(props, { checked }) => ( <span {...props}> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> {checked ? "Enabled" : "Disabled"} </Switch.Content> </span> )} /> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
渲染函数
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Switch } from "@lenso/ui";export function RenderFunction() { return ( <Switch render={(props) => <div {...props} data-custom="foo" />}> <Switch.Content> <Switch.Control> <Switch.Thumb /> </Switch.Control> Enable notifications </Switch.Content> </Switch> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Description, Label, Switch, TextField } from "@lenso/ui";import { switchSupportingStyles } from "@lenso/tokens/switch";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ control: { "--switch-control-bg-checked": "var(--success)", "--switch-control-bg-checked-hover": "var(--success)", }, copy: { display: "flex", flexDirection: "column", gap: ".125rem" },});export function CustomStyles() { return ( <TextField> <Switch id="autosave" aria-describedby="autosave-help"> <Switch.Content> <Switch.Control xstyle={styles.control}> <Switch.Thumb /> </Switch.Control> <div {...stylex.props(styles.copy)}> <Label>Auto-save drafts</Label> <Description id="autosave-help" xstyle={switchSupportingStyles.direct}> Changes are saved as you type. </Description> </div> </Switch.Content> </Switch> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
若要自定义组件类,可使用 @layer components 指令。了解更多。
@layer components { .switch { @apply inline-flex gap-3 items-center; }
.switch__control { @apply h-5 w-8 bg-gray-400 data-[selected=true]:bg-blue-500; }
.switch__thumb { @apply bg-white shadow-sm; }
.switch__content { @apply flex flex-col gap-1; }
.switch__icon { @apply h-3 w-3 text-current; }}样式参考
HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。
CSS 类
Switch 类 [!toc]
Switch 组件使用以下 CSS 类(查看源码样式):
.switch- Switch 根容器(字段).switch__content- 包裹控件与标签文本的可点击 label.switch__control- Switch 轨道.switch__thumb- 可移动的滑块.switch__icon- 滑块内可选图标.switch--sm- 小尺寸变体.switch--md- 中尺寸变体(默认).switch--lg- 大尺寸变体
SwitchGroup 类 [!toc]
SwitchGroup 组件使用以下 CSS 类(查看源码样式):
.switch-group- Switch 组容器.switch-group__items- Switch 项容器.switch-group--horizontal- 横向布局.switch-group--vertical- 纵向布局(默认)
交互状态
该 Switch 同时支持 CSS 伪类与 data 属性,以提供更灵活的状态控制:
- 已选中:
[data-selected="true"](滑块位置与背景色变化) - 悬停:
:hover或[data-hovered="true"](作用于Switch.Control/ 按钮) - 聚焦:
:focus-visible或[data-focus-visible="true"](在按钮上显示轨道焦点环) - 禁用:
[data-disabled="true"](降低透明度,包括帮助文本) - 按压:
:active或[data-pressed="true"]
API 参考
Switch
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Switch 尺寸。 |
isSelected | boolean | false | Switch 是否打开。 |
defaultSelected | boolean | false | 默认是否打开(非受控)。 |
isDisabled | boolean | false | Switch 是否禁用。 |
isInvalid | boolean | false | Switch 是否无效。 |
isReadOnly | boolean | false | Switch 是否只读。 |
isRequired | boolean | false | Switch 是否必须打开。 |
validate | (value: boolean) => ValidationError | true | null | undefined | - | 自定义校验函数。 |
validationBehavior | 'native' | 'aria' | 'native' | 使用原生 HTML 校验或 ARIA 校验。 |
name | string | - | 输入元素名称,用于提交 HTML 表单。 |
value | string | - | 输入元素值,用于提交 HTML 表单。 |
onChange | (isSelected: boolean) => void | - | Switch 值变化时的事件处理函数。 |
onPress | (e: PressEvent) => void | - | Switch 被按下时的事件处理函数。 |
children | React.ReactNode | (values: SwitchFieldRenderProps) => React.ReactNode | - | Switch 内容或字段级渲染 prop。 |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, SwitchFieldRenderProps> | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
Switch.Content
包裹控件与标签文本的可点击 <label>。请把 Switch.Control 与 Label 放在它内部;Description/FieldError 作为 Switch.Content 的兄弟节点。对于没有标签的 switch,省略 Label 并在 Switch 上传入 aria-label。
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
children | React.ReactNode | (values: SwitchButtonRenderProps) => React.ReactNode | - | 按钮内容(控件 + 标签),或按钮级渲染 prop |
className | string | (values: SwitchButtonRenderProps) => string | - | 应用到可点击 label 的类名 |
SwitchFieldRenderProps
在根 Switch 上使用渲染 prop 时,提供以下字段级值:
| Prop | 类型 | 描述 |
|---|---|---|
isSelected | boolean | Switch 当前是否打开。 |
isDisabled | boolean | Switch 是否禁用。 |
isReadOnly | boolean | Switch 是否只读。 |
isInvalid | boolean | Switch 是否无效。 |
isRequired | boolean | Switch 是否必填。 |
state | ToggleState | Switch 的状态。 |
SwitchButtonRenderProps
Switch.Control 使用按钮级渲染 prop(isHovered、isPressed、isFocusVisible 等)。将函数作为 Switch.Control 的子元素即可访问。
SwitchGroup
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'vertical' | Switch 组方向。 |
children | React.ReactNode | - | 要渲染的 Switch 项。 |
className | string | - | 额外的 CSS 类。 |