Skyroc Native UI

Switch

立即生效的开关,支持加载态与自定义滑块内容

开关(Switch)用于在两个互斥状态之间即时切换。轨道是一个 Pressable,未选中底色直接挂在轨道上,选中色是叠在上面的一层 Animated.View,通过透明度淡入 —— 两层都用语义色 token,主题与暗色模式自动跟随,不需要在动画 worklet 里插值色值。滑块位移与选中色淡入共用 200ms 的 withTiming

import { Switch } from '@skyroc/native-ui';

基础用法

checked + onCheckedChange 即为受控用法,这也是开关最常见的写法。

SwitchBasic.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const SwitchBasic = () => {
  const [basic, setBasic] = useState(false);

  return (
    <View className="flex-row items-center gap-3 bg-background p-4">
      <Switch
        checked={basic}
        onCheckedChange={setBasic}
      />
      <Text color="muted">当前状态:{basic ? '开' : '关'}</Text>
    </View>
  );
};

export { SwitchBasic };

何时使用

  • 设置项、开关型偏好这类「改完立刻生效」的场景。
  • 需要用户确认后才生效的选择请用 Checkbox;一组互斥选项请用 Radio
  • 切换要走网络请求时,配合 loading 使用(见下文「异步切换」),不要让 UI 先翻转再回滚。

非受控

只传 defaultChecked 时选中态由组件内部维护,适合表单里由 Form 统一收集值的场景。

SwitchUncontrolled.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import { View } from 'react-native';

const SwitchUncontrolled = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <View className="flex-row items-center gap-3">
        <Switch defaultChecked />
        <Text color="muted">defaultChecked=true</Text>
      </View>
      <View className="flex-row items-center gap-3">
        <Switch />
        <Text color="muted">defaultChecked=false</Text>
      </View>
    </View>
  );
};

export { SwitchUncontrolled };

尺寸

size 同时决定轨道宽高与滑块直径,滑块四周留白固定为 2:

尺寸轨道(宽 × 高)滑块滑块位移
xs28 × 161212
sm32 × 181414
md36 × 201616
lg40 × 221818
xl44 × 242020
2xl52 × 282424
SwitchSize.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import type { ThemeSize } from '@skyroc/native-ui';
import { View } from 'react-native';

const SIZES: ThemeSize[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];

const SwitchSize = () => {
  return (
    <View className="gap-3 bg-background p-4">
      {SIZES.map(size => (
        <View
          key={size}
          className="flex-row items-center gap-3"
        >
          <Switch
            defaultChecked
            size={size}
          />
          <Text color="muted">{size}</Text>
        </View>
      ))}
    </View>
  );
};

export { SwitchSize };

尺寸不做成样式变体:轨道与滑块是像素级联动(内边距和位移距离都由两者算出),走尺寸映射表比拆成类名更直接。

语义颜色

color 决定开启状态的轨道颜色,同时决定 loading 指示器的颜色:

颜色开启轨道语义
primarybg-primary常规开关(默认)
successbg-success启用、已生效
warningbg-warning需要注意的开关
destructivebg-destructive危险开关,如关闭保护
infobg-info信息性开关
accentbg-accent强调色
carbonbg-carbon中性深色
secondarybg-secondary次级、弱化开关
SwitchColor.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import type { ThemeColor } from '@skyroc/native-ui';
import { View } from 'react-native';

const COLORS: ThemeColor[] = ['primary', 'success', 'warning', 'destructive', 'info', 'accent', 'carbon', 'secondary'];

const SwitchColor = () => {
  return (
    <View className="gap-3 bg-background p-4">
      {COLORS.map(color => (
        <View
          key={color}
          className="flex-row items-center gap-3"
        >
          <Switch
            defaultChecked
            color={color}
          />
          <Text color="muted">{color}</Text>
        </View>
      ))}
    </View>
  );
};

export { SwitchColor };

关闭态与 color 无关,统一是 bg-muted-foreground/30。指示器颜色取 accent-{color},只有 secondaryaccent-secondary-foreground —— 浅色轨道上用同色指示器会看不见。

禁用

disabled 阻止切换并把整体降到 50% 不透明度,开启与关闭态都适用。

SwitchDisabled.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import { View } from 'react-native';

const SwitchDisabled = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <View className="flex-row items-center gap-3">
        <Switch disabled />
        <Text color="muted">禁用·关闭</Text>
      </View>
      <View className="flex-row items-center gap-3">
        <Switch
          defaultChecked
          disabled
        />
        <Text color="muted">禁用·开启</Text>
      </View>
    </View>
  );
};

export { SwitchDisabled };

加载

loading 在滑块内渲染一个 ActivityIndicator,同时阻止点击(与 disabled 等价)。指示器按滑块尺寸缩放:iOS 的 ActivityIndicator 会忽略数字 size(只撑大外框、指示器本身仍是固有尺寸),所以统一用 size="small" 再做 scale,两端表现才一致。

SwitchLoading.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import { View } from 'react-native';

const SwitchLoading = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <View className="flex-row items-center gap-3">
        <Switch loading />
        <Text color="muted">加载中·关闭</Text>
      </View>
      <View className="flex-row items-center gap-3">
        <Switch
          defaultChecked
          loading
        />
        <Text color="muted">加载中·开启</Text>
      </View>
      <View className="flex-row items-center gap-3">
        <Switch
          defaultChecked
          loading
          size="2xl"
        />
        <Text color="muted">2xl 加载指示器</Text>
      </View>
    </View>
  );
};

export { SwitchLoading };

异步切换

受控 + loading 的组合可以做到「请求成功后再翻转」:onCheckedChange 只发起请求并打开 loadingchecked 等落库成功后才更新。

SwitchAsync.tsx
Loading…
import { Switch, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const SwitchAsync = () => {
  const [pending, setPending] = useState(false);
  const [submitting, setSubmitting] = useState(false);

  /** 模拟一次异步落库:期间保持 loading,成功后再翻转 */
  function handlePendingChange(next: boolean) {
    setSubmitting(true);

    setTimeout(() => {
      setPending(next);
      setSubmitting(false);
    }, 1200);
  }

  return (
    <View className="flex-row items-center gap-3 bg-background p-4">
      <Switch
        checked={pending}
        loading={submitting}
        size="lg"
        onCheckedChange={handlePendingChange}
      />
      <Text color="muted">{submitting ? '保存中…' : `已保存:${pending ? '开' : '关'}`}</Text>
    </View>
  );
};

export { SwitchAsync };

自定义滑块内容

children 渲染在滑块内部,string / number 会自动包一层 Textloading 时指示器优先,children 被临时隐藏。滑块空间很小,内容尺寸请自行控制(2xl 也只有 24px)。

SwitchThumb.tsx
Loading…
import Ionicons from '@expo/vector-icons/Ionicons';
import { Switch, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';

/** 与库内一致的取色方式:`accent-*` 工具类映射到矢量图标的 color 上 */
const ThumbIcon = withUniwind(Ionicons);

const SwitchThumb = () => {
  return (
    <View className="flex-row items-center gap-3 bg-background p-4">
      <Switch
        defaultChecked
        size="2xl"
      >
        <ThumbIcon
          colorClassName="accent-primary"
          name="checkmark"
          size={14}
        />
      </Switch>
      <Switch size="2xl">
        <Text className="text-[10px] text-muted-foreground">off</Text>
      </Switch>
    </View>
  );
};

export { SwitchThumb };

样式覆盖

className 追加到轨道容器上,classNames 按 slot 细粒度覆盖:

slot作用位置
root轨道容器 Pressable,同时承载关闭态底色
checkedOverlay叠在轨道上的选中色层,透明度由动画驱动
thumb滑块(背景、阴影、圆角)
indicatorloading 指示器的 colorClassName,只接受 accent-* 颜色类
SwitchStyles.tsx
Loading…
import { Switch } from '@skyroc/native-ui';
import { View } from 'react-native';

const SwitchStyles = () => {
  return (
    <View className="flex-row items-center gap-3 bg-background p-4">
      <Switch
        className="bg-warning/30"
        defaultChecked={false}
      />
      <Switch
        classNames={{
          checkedOverlay: 'bg-info',
          thumb: 'bg-info-50'
        }}
        defaultChecked
      />
      <Switch
        loading
        classNames={{ indicator: 'accent-destructive' }}
        size="2xl"
      />
    </View>
  );
};

export { SwitchStyles };

className 排在 classNames.root 之后参与合并,冲突时 className 优先。轨道宽高与滑块尺寸走的是内联 style,类名改不动,需要非标准尺寸时请用 size

外部控制

受控状态可以从组件外任意更新 —— 开关只是这份状态的一个视图。

SwitchControlled.tsx
Loading…
import { Button, Switch, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const SwitchControlled = () => {
  const [controlled, setControlled] = useState(true);

  return (
    <View className="gap-3 bg-background p-4">
      <View className="flex-row items-center gap-3">
        <Switch
          checked={controlled}
          color="success"
          onCheckedChange={setControlled}
        />
        <Text color="muted">{controlled ? '已开启' : '已关闭'}</Text>
      </View>
      <View className="flex-row gap-2">
        <Button
          color="primary"
          variant="outline"
          onPress={() => setControlled(true)}
        >
          开启
        </Button>
        <Button
          color="primary"
          variant="outline"
          onPress={() => setControlled(false)}
        >
          关闭
        </Button>
        <Button
          color="primary"
          variant="ghost"
          onPress={() => setControlled(!controlled)}
        >
          取反
        </Button>
      </View>
    </View>
  );
};

export { SwitchControlled };

热区与无障碍

轨道带 hitSlop={4}xs / sm 这类小尺寸也有足够的点击区域。组件目前没有设置 accessibilityRole / accessibilityState,读屏器不会播报开关状态;需要完整读屏语义时,暂时只能在外层自行包裹带 accessibilityRole="switch" 的容器。

API

Switch

属性说明类型默认值
checked受控选中态boolean-
defaultChecked非受控初始选中态booleanfalse
onCheckedChange选中态变化回调(checked: boolean) => void-
color开启状态的语义色,同时决定 loading 指示器颜色'primary' | 'destructive' | 'success' | 'warning' | 'info' | 'accent' | 'carbon' | 'secondary''primary'
size尺寸预设,决定轨道宽高与滑块直径'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl''md'
disabled禁用,阻止切换并降低不透明度booleanfalse
loading加载态,滑块内显示指示器并阻止点击booleanfalse
children滑块内容,string / number 会被自动包裹为 Text;loading 时由指示器替代ReactNode-
className轨道容器类名,合并在 classNames.root 之后string-
classNames各 slot 的类名覆盖,见「样式覆盖」一节SlotClassNames<SwitchSlots>-
testID测试标识,挂在轨道容器上string-
ref轨道容器(Pressable)的 ref,用于 measure / 滚动定位Ref<View>-

类型

import type { SwitchProps, SwitchSlots } from '@skyroc/native-ui';

SwitchSlots

可通过 classNames 覆盖的 slot 名称。

'checkedOverlay' | 'indicator' | 'root' | 'thumb'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 SwitchSlots。

Partial<Record<Slots, string>>

包内还导出了 switchVariants 与两张尺寸映射表 SWITCH_SIZE_TRACK_MAP(轨道宽高)、SWITCH_SIZE_THUMB_MAP(滑块直径)。