Bläddra i källkod

feat(a11y): switch aria #205 (#413)

pointhalo 3 år sedan
förälder
incheckning
2d6202a96b

+ 83 - 70
content/input/switch/index-en-US.md

@@ -2,7 +2,7 @@
 localeCode: en-US
 order: 27
 category: Input
-title:  Switch
+title: Switch
 subTitle: Switch
 icon: doc-switch
 width: 60%
@@ -10,24 +10,27 @@ brief: Switch is an interactive form used to switch two mutually exclusive state
 ---
 
 ## Demos
+
 ### How to import
 
 ```jsx import
 import { Switch } from '@douyinfe/semi-ui';
 ```
+
 ### Basic Usage
 
+You can monitor state changes through `onChange`, and set the selected state through `defaultChecked` or controlled `checked`.  
+Use `aria-label` to describe the specific function of the Switch
+
 ```jsx live=true
 import React from 'react';
 import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch onChange={(v, e) => console.log(v)}>
-        </Switch>
-        <br/>
-        <Switch defaultChecked={true} onChange={(v, e) => console.log(v)}>
-        </Switch>
+        <Switch onChange={(v, e) => console.log(v)} aria-label="a switch for demo"></Switch>
+        <br />
+        <Switch defaultChecked={true} onChange={(v, e) => console.log(v)} aria-label="a switch for demo"></Switch>
     </div>
 );
 ```
@@ -40,20 +43,22 @@ import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch size='small'></Switch>
-        <Switch defaultChecked={true} size='small'></Switch>
-        <Switch size='small' loading/>
-        <Switch size='small' loading defaultChecked={true} />
-        <br/><br/>
+        <Switch size="small" aria-label="a switch for demo"></Switch>
+        <Switch defaultChecked={true} size="small" aria-label="a switch for demo"></Switch>
+        <Switch size="small" loading aria-label="a switch for demo" />
+        <Switch size="small" loading defaultChecked={true} aria-label="a switch for demo" />
+        <br />
+        <br />
         <Switch></Switch>
         <Switch defaultChecked={true}></Switch>
         <Switch loading />
         <Switch loading defaultChecked={true} />
-        <br/><br/>
-        <Switch size='large'></Switch>
-        <Switch defaultChecked={true} size='large'></Switch>
-        <Switch size='large' loading/>
-        <Switch size='large' loading defaultChecked={true} />
+        <br />
+        <br />
+        <Switch size="large"></Switch>
+        <Switch defaultChecked={true} size="large"></Switch>
+        <Switch size="large" loading />
+        <Switch size="large" loading defaultChecked={true} />
     </div>
 );
 ```
@@ -66,9 +71,9 @@ import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch disabled></Switch>
-        <br/>
-        <Switch disabled checked={true}></Switch>
+        <Switch disabled aria-label='a switch for demo'></Switch>
+        <br />
+        <Switch disabled checked={true} aria-label='a switch for demo'></Switch>
     </div>
 );
 ```
@@ -85,22 +90,26 @@ import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch checkedText='on' uncheckedText='off' />
-        <Switch checkedText='|' uncheckedText='〇' style={{marginLeft:5}}/>
-        <br/><br/>
-        <Switch defaultChecked checkedText='on' uncheckedText='off' />
-        <Switch defaultChecked checkedText='|' uncheckedText='〇' style={{marginLeft:5}}/>
-        <br/><br/>
-        <Switch checkedText='on' uncheckedText='off' size='large' />
-        <Switch checkedText='|' uncheckedText='〇' size='large' style={{marginLeft:5}}/>
-        <br/><br/>
-        <Switch defaultChecked checkedText='on' uncheckedText='off' size='large' />
-        <Switch defaultChecked checkedText='|' uncheckedText='〇' size='large' style={{marginLeft:5}}/>
+        <Switch checkedText="on" uncheckedText="off" />
+        <Switch checkedText="|" uncheckedText="〇" style={{ marginLeft: 5 }} />
+        <br />
+        <br />
+        <Switch defaultChecked checkedText="on" uncheckedText="off" />
+        <Switch defaultChecked checkedText="|" uncheckedText="〇" style={{ marginLeft: 5 }} />
+        <br />
+        <br />
+        <Switch checkedText="on" uncheckedText="off" size="large" />
+        <Switch checkedText="|" uncheckedText="〇" size="large" style={{ marginLeft: 5 }} />
+        <br />
+        <br />
+        <Switch defaultChecked checkedText="on" uncheckedText="off" size="large" />
+        <Switch defaultChecked checkedText="|" uncheckedText="〇" size="large" style={{ marginLeft: 5 }} />
     </div>
 );
 ```
 
 Compared to setting the embedded text through checkedText and uncheckedText, we recommend placing the text description outside the Switch
+
 ```jsx live=true
 import React, { useState } from 'react';
 import { Switch, Typography } from '@douyinfe/semi-ui';
@@ -109,9 +118,11 @@ import { Switch, Typography } from '@douyinfe/semi-ui';
     const [open, setOpen] = useState();
     const { Title } = Typography;
     return (
-        <div style={{display:'flex', alignItems: 'center'}}>
-            <Title heading={6} style={{margin: 8}}>{open?'Open':'Closed'}</Title>
-            <Switch checked={open} onChange={setOpen}/>
+        <div style={{ display: 'flex', alignItems: 'center' }}>
+            <Title heading={6} style={{ margin: 8 }}>
+                {open ? 'Open' : 'Closed'}
+            </Title>
+            <Switch checked={open} onChange={setOpen} />
         </div>
     );
 };
@@ -125,27 +136,21 @@ Whether the component is selected depends entirely on the incoming checked value
 import React from 'react';
 import { Switch } from '@douyinfe/semi-ui';
 
-class Demo extends React.Component {
-    constructor() {
-        super();
-        this.state = {
-            checked: true,
-        };
-        this.onChange = this.onChange.bind(this);
-    }
-    onChange(checked) {
-        this.setState({ checked });
-    }
-    render() {
-        return (
-            <>
-                <Switch
-                    checked={this.state.checked}
-                    onChange={this.onChange}>
-                </Switch>
-            </>
-        );
-    }
+() => {
+    const [checked, setChecked] = useState(true);
+
+    const onChange = (checked) => {
+        setChecked(checked);
+    };
+
+    return (
+        <Switch
+            checked={checked}
+            aria-label='a switch for demo'
+            onChange={onChange}
+        />
+    );
+
 }
 ```
 
@@ -170,20 +175,28 @@ import { Switch } from '@douyinfe/semi-ui';
 
 ## API reference
 
-| Properties     | Instructions                                              | Type                        | Default   | version|
-| -------------- | --------------------------------------------------------- | --------------------------- | --------- | ------ |
-| className      | The CSS class name of the wrapper element                 | string                      |           ||
-| checked        | Indicates whether currently selected, used with onchange  | boolean                     | false     ||
-| checkedText    | Content displayed when open, invalid when size is small   | React Node                  |           |0.25.0|
-| defaultChecked | Whether selected when component mounted                   | boolean                     | false     ||
-| disabled       | If true, the switch will be disabled.                     | boolean                     | false     ||
-| loading        | Turn on loading status                                    | boolean                     | false     |1.29.0|
-| onChange       | Callback function when changing                           | function (checked: boolean) |           ||
-| onMouseEnter   | A callback when the mouse moves in                        | function ()                 |           ||
-| onMouseLeave   | A callback when the mouse moves out                       | function ()                 |           ||
-| size           | Size, optional value `large`, `default`, `small`          | string                      | 'default' ||
-| style          | Inline style                                              | object                      | {}        ||
-| uncheckedText  | Content displayed when closed, invalid when size is small | React Node                  |           |0.25.0|
-
+| Properties | Instructions | Type | Default | version |
+| --- | --- | --- | --- | --- |
+| aria-label | [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute) used to define a string that labels the current element. Use it in cases where a text label is not visible on the screen | string |  | 2.2.0 |
+| aria-labelledby | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute)attribute establishes relationships between objects and their label(s), and its value should be one or more element IDs, which refer to elements that have the text needed for labeling. | string |  | 2.2.0 |
+| className | The CSS class name of the wrapper element | string |  |  |
+| checked | Indicates whether currently selected, used with onchange | boolean | false |  |
+| checkedText | Content displayed when open, invalid when size is small | React Node |  | 0.25.0 |
+| defaultChecked | Whether selected when component mounted | boolean | false |  |
+| disabled | If true, the switch will be disabled. | boolean | false |  |
+| loading | Turn on loading status | boolean | false | 1.29.0 |
+| onChange | Callback function when changing | function (checked: boolean) |  |  |
+| onMouseEnter | A callback when the mouse moves in | function () |  |  |
+| onMouseLeave | A callback when the mouse moves out | function () |  |  |
+| size | Size, optional value `large`, `default`, `small` | string | 'default' |  |
+| style | Inline style | object | {} |  |
+| uncheckedText | Content displayed when closed, invalid when size is small | React Node |  | 0.25.0 |
+
+## Accessibility
+### Aria
+- Switch has a `switch` role, when checked is true, `aria-checked` will be automatically set to true, and vice versa.
+- As a form field, it should have a Label, which will be automatically brought on when you use Form.Switch.
+- If you use Switch alone, it is recommended to use `aria-label` to describe the current label function.
 ## Design Tokens
-<DesignToken/>
+
+<DesignToken/>

+ 88 - 78
content/input/switch/index.md

@@ -2,13 +2,12 @@
 localeCode: zh-CN
 order: 27
 category: 输入类
-title:  Switch 开关
+title: Switch 开关
 icon: doc-switch
 width: 60%
 brief: 开关是用于切换两种互斥状态的交互形式
 ---
 
-
 ## 代码演示
 
 ### 如何引入
@@ -16,45 +15,53 @@ brief: 开关是用于切换两种互斥状态的交互形式
 ```jsx import
 import { Switch } from '@douyinfe/semi-ui';
 ```
+
 ### 基本
 
+你可以通过 `onChange` 监听状态变化,通过 `defaultChecked` 或受控的 `checked` 制定选中状态。  
+通过 `aria-label` 描述该 Switch 开关的具体作用
+
 ```jsx live=true
 import React from 'react';
 import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch onChange={(v, e) => console.log(v)}>
-        </Switch>
-        <br/>
-        <Switch defaultChecked={true} onChange={(v, e) => console.log(v)}>
-        </Switch>
+        <Switch onChange={(v, e) => console.log(v)} aria-label="a switch for demo"></Switch>
+        <br />
+        <Switch defaultChecked={true} onChange={(v, e) => console.log(v)} aria-label="a switch for semi demo"></Switch>
     </div>
 );
 ```
 
 ### 尺寸
 
+你可以通过 size 指定尺寸
+
 ```jsx live=true
 import React from 'react';
 import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch size='small'></Switch>
-        <Switch defaultChecked={true} size='small'></Switch>
-        <Switch size='small' loading/>
-        <Switch size='small' loading defaultChecked={true} />
-        <br/><br/>
-        <Switch></Switch>
-        <Switch defaultChecked={true}></Switch>
-        <Switch loading />
-        <Switch loading defaultChecked={true} />
-        <br/><br/>
-        <Switch size='large'></Switch>
-        <Switch defaultChecked={true} size='large'></Switch>
-        <Switch size='large' loading/>
-        <Switch size='large' loading defaultChecked={true} />
+        <Space style={{ marginBottom: 10, display: 'block' }}>
+            <Switch size="small" aria-label="a switch for demo"></Switch>
+            <Switch defaultChecked={true} size="small" aria-label="a switch for demo"></Switch>
+            <Switch size="small" loading aria-label="a switch for demo" />
+            <Switch size="small" loading defaultChecked={true} aria-label="a switch for demo" />
+        </Space>
+        <Space style={{ marginBottom: 10, display: 'block' }}>
+            <Switch></Switch>
+            <Switch defaultChecked={true} aria-label="a switch for demo"></Switch>
+            <Switch loading aria-label="a switch for demo" />
+            <Switch loading defaultChecked={true} aria-label="a switch for demo" />
+        </Space>
+        <Space>
+            <Switch size="large"></Switch>
+            <Switch defaultChecked={true} size="large"></Switch>
+            <Switch size="large" loading />
+            <Switch size="large" loading defaultChecked={true} />
+        </Space>
     </div>
 );
 ```
@@ -67,9 +74,9 @@ import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch disabled></Switch>
-        <br/>
-        <Switch disabled checked={true}></Switch>
+        <Switch disabled aria-label="a switch for demo"></Switch>
+        <br />
+        <Switch disabled checked={true} aria-label="a switch for demo"></Switch>
     </div>
 );
 ```
@@ -77,8 +84,7 @@ import { Switch } from '@douyinfe/semi-ui';
 ### 带文本
 
 可以通过 `checkedText` 与 `uncheckedText` 设置开关时的文本  
-注意:此项功能在最小的开关(即 size='small'时)无效  
-
+注意:此项功能在最小的开关(即 size='small'时)无效
 
 ```jsx live=true
 import React from 'react';
@@ -86,22 +92,26 @@ import { Switch } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Switch checkedText='开' uncheckedText='关' />
-        <Switch checkedText='|' uncheckedText='〇' style={{marginLeft:5}}/>
-        <br/><br/>
-        <Switch defaultChecked checkedText='开' uncheckedText='关' />
-        <Switch defaultChecked checkedText='|' uncheckedText='〇' style={{marginLeft:5}}/>
-        <br/><br/>
-        <Switch checkedText='开' uncheckedText='关' size='large' />
-        <Switch checkedText='|' uncheckedText='〇' size='large' style={{marginLeft:5}}/>
-        <br/><br/>
-        <Switch defaultChecked checkedText='开' uncheckedText='关' size='large' />
-        <Switch defaultChecked checkedText='|' uncheckedText='〇' size='large' style={{marginLeft:5}}/>
+        <Switch checkedText="开" uncheckedText="关" />
+        <Switch checkedText="|" uncheckedText="〇" style={{ marginLeft: 5 }} />
+        <br />
+        <br />
+        <Switch defaultChecked checkedText="开" uncheckedText="关" />
+        <Switch defaultChecked checkedText="|" uncheckedText="〇" style={{ marginLeft: 5 }} />
+        <br />
+        <br />
+        <Switch checkedText="开" uncheckedText="关" size="large" />
+        <Switch checkedText="|" uncheckedText="〇" size="large" style={{ marginLeft: 5 }} />
+        <br />
+        <br />
+        <Switch defaultChecked checkedText="开" uncheckedText="关" size="large" />
+        <Switch defaultChecked checkedText="|" uncheckedText="〇" size="large" style={{ marginLeft: 5 }} />
     </div>
 );
 ```
 
-相比于通过chekedText与uncheckedText设置内嵌的文本,我们更推荐将文本说明放置在Switch外部
+相比于通过 chekedText 与 uncheckedText 设置内嵌的文本,我们更推荐将文本说明放置在 Switch 外部
+
 ```jsx live=true
 import React, { useState } from 'react';
 import { Switch, Typography } from '@douyinfe/semi-ui';
@@ -110,9 +120,11 @@ import { Switch, Typography } from '@douyinfe/semi-ui';
     const [open, setOpen] = useState();
     const { Title } = Typography;
     return (
-        <div style={{display:'flex', alignItems: 'center'}}>
-            <Title heading={6} style={{margin: 8}}>{open?'已开启':'已关闭'}</Title>
-            <Switch checked={open} onChange={setOpen}/>
+        <div style={{ display: 'flex', alignItems: 'center' }}>
+            <Title heading={6} style={{ margin: 8 }}>
+                {open ? '已开启' : '已关闭'}
+            </Title>
+            <Switch checked={open} onChange={setOpen} aria-label="a switch for demo" />
         </div>
     );
 };
@@ -126,28 +138,15 @@ import { Switch, Typography } from '@douyinfe/semi-ui';
 import React from 'react';
 import { Switch } from '@douyinfe/semi-ui';
 
-class Demo extends React.Component {
-    constructor() {
-        super();
-        this.state = {
-            checked: true,
-        };
-        this.onChange = this.onChange.bind(this);
-    }
-    onChange(checked) {
-        this.setState({ checked });
-    }
-    render() {
-        return (
-            <>
-                <Switch
-                    checked={this.state.checked}
-                    onChange={this.onChange}>
-                </Switch>
-            </>
-        );
-    }
-}
+() => {
+    const [checked, setChecked] = useState(true);
+
+    const onChange = checked => {
+        setChecked(checked);
+    };
+
+    return <Switch checked={checked} aria-label="a switch for demo" onChange={onChange} />;
+};
 ```
 
 ### 加载中
@@ -171,20 +170,31 @@ import { Switch } from '@douyinfe/semi-ui';
 
 ## API 参考
 
-| 属性           | 说明                                     | 类型                      | 默认值    |版本|
-| -------------- | ---------------------------------------- | ------------------------- | --------- |--------- |
-| className      | 类名                                     | string                    |           |
-| checked        | 指示当前是否选中,配合 onChange 使用      | boolean                   | false     ||
-| checkedText    | 打开时展示的内容, size为small时无效 | ReactNode                 |           |0.25.0|
-| defaultChecked | 初始是否选中                             | boolean                   | false     ||
-| disabled       | 是否禁用                                 | boolean                   | false     ||
-| loading        | 设置加载状态                                 | boolean                   | false     |1.29.0|
-| onChange       | 变化时回调函数                           | function(checked:boolean) |           ||
-| onMouseEnter   | 鼠标移入时回调                        | function()                |           ||
-| onMouseLeave   | 鼠标移出时回调                        | function()                |           ||
-| size           | 尺寸,可选值`large`,`default`,`small`     | string                    | 'default' ||
-| style           | 内联样式     | object                    | ||
-| uncheckedText  | 关闭时展示的内容, size为small时无效 | ReactNode                 |           |0.25.0|
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| aria-label | [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute)属性,用来给当前元素加上的标签描述, 提升可访问性 | string |  | 2.2.0 |
+| aria-labelledby | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute)属性,表明某些元素的 id 是某一对象的标签。它被用来确定控件或控件组与它们标签之间的联系, 提升可访问性 | string |  | 2.2.0 |
+| className | 类名 | string |  |
+| checked | 指示当前是否选中,配合 onChange 使用 | boolean | false |  |
+| checkedText | 打开时展示的内容, size 为 small 时无效 | ReactNode |  |  |
+| defaultChecked | 初始是否选中 | boolean | false |  |
+| disabled | 是否禁用 | boolean | false |  |
+| loading | 设置加载状态 | boolean | false | 1.29.0 |
+| onChange | 变化时回调函数 | function(checked:boolean) |  |  |
+| onMouseEnter | 鼠标移入时回调 | function() |  |  |
+| onMouseLeave | 鼠标移出时回调 | function() |  |  |
+| size | 尺寸,可选值`large`,`default`,`small` | string | 'default' |  |
+| style | 内联样式 | object |  |  |
+| uncheckedText | 关闭时展示的内容, size 为 small 时无效 | ReactNode |  |  |
+
+## Accessibility
+
+### Aria
+
+-   Switch 具有 `switch` role,当 checked 为 true 时,`aria-checked` 将被自动设置为 true,反之亦然.
+-   作为表单控件应该带有 Label,当你使用 Form.Switch 时会自动被带上。
+-   如果你单独使用 Switch,建议使用 `aria-label` 描述当前标签作用。
 
 ## 设计变量
-<DesignToken/>
+
+<DesignToken/>

+ 2 - 3
packages/semi-ui/spin/icon.tsx

@@ -38,11 +38,10 @@ function Icon(props: IconProps = {}) {
                     <stop stopColor="currentColor" offset="100%" />
                 </linearGradient>
             </defs>
-            <g id="Artboard" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
-                <rect id="Rectangle" fillOpacity="0.01" fill="#FFFFFF" x="0" y="0" width="36" height="36" />
+            <g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
+                <rect fillOpacity="0.01" fill="#FFFFFF" x="0" y="0" width="36" height="36" />
                 <path
                     d="M34,18 C34,9.163444 26.836556,2 18,2 C11.6597233,2 6.18078805,5.68784135 3.59122325,11.0354951"
-                    id="Path"
                     stroke={`url(#${id})`}
                     strokeWidth="4"
                     strokeLinecap="round"

+ 20 - 19
packages/semi-ui/switch/_story/switch.stories.js

@@ -9,8 +9,8 @@ export default {
 
 export const _Switch = () => (
   <div>
-    <Switch onChange={(v, e) => console.log(v)}></Switch>
-    <Switch defaultChecked={true} onChange={(v, e) => console.log(v)}></Switch>
+    <Switch onChange={(v, e) => console.log(v)} aria-label='power-switch'></Switch>
+    <Switch defaultChecked={true} onChange={(v, e) => console.log(v)} aria-label='mode-switch'></Switch>
   </div>
 );
 
@@ -20,9 +20,9 @@ _Switch.story = {
 
 export const SwitchSize = () => (
   <div>
-    <Switch onChange={(v, e) => console.log(v)}></Switch>
-    <Switch onChange={(v, e) => console.log(v)} size="small"></Switch>
-    <Switch onChange={(v, e) => console.log(v)} size="large"></Switch>
+    <Switch onChange={(v, e) => console.log(v)} aria-label='power-switch'></Switch>
+    <Switch onChange={(v, e) => console.log(v)} size="small" aria-label='power-switch'></Switch>
+    <Switch onChange={(v, e) => console.log(v)} size="large" aria-label='power-switch'></Switch>
   </div>
 );
 
@@ -32,20 +32,20 @@ SwitchSize.story = {
 
 export const SwitchCheckedTextUncheckedText = () => (
   <div>
-    <Switch defaultChecked checkedText="开" uncheckedText="关" />
-    <Switch checkedText={'|'} uncheckedText="〇" />
+    <Switch defaultChecked checkedText="开" uncheckedText="关" aria-label='power-switch'/>
+    <Switch checkedText={'|'} uncheckedText="〇" aria-label='power-switch'/>
     <br />
     <br />
-    <Switch checkedText="开" uncheckedText="关" />
-    <Switch defaultChecked checkedText="|" uncheckedText="〇" />
+    <Switch checkedText="开" uncheckedText="关" aria-label='power-switch'/>
+    <Switch defaultChecked checkedText="|" uncheckedText="〇" aria-label='power-switch'/>
     <br />
     <br />
-    <Switch checkedText="开" uncheckedText="关" size="large" />
-    <Switch checkedText="|" uncheckedText="〇" size="large" />
+    <Switch checkedText="开" uncheckedText="关" size="large" aria-label='power-switch'/>
+    <Switch checkedText="|" uncheckedText="〇" size="large" aria-label='power-switch'/>
     <br />
     <br />
-    <Switch defaultChecked checkedText="开" uncheckedText="关" size="large" />
-    <Switch defaultChecked checkedText="|" uncheckedText="〇" size="large" />
+    <Switch defaultChecked checkedText="开" uncheckedText="关" size="large" aria-label='power-switch'/>
+    <Switch defaultChecked checkedText="|" uncheckedText="〇" size="large" aria-label='power-switch'/>
   </div>
 );
 
@@ -55,9 +55,9 @@ SwitchCheckedTextUncheckedText.story = {
 
 export const SwitchDisabled = () => (
   <>
-    <Switch disabled>disabled</Switch>
+    <Switch disabled aria-label='power-switch'>disabled</Switch>
 
-    <Switch disabled checked={true} onChange={(v, e) => console.log(v)}></Switch>
+    <Switch disabled checked={true} onChange={(v, e) => console.log(v)} aria-label='power-switch'></Switch>
   </>
 );
 
@@ -67,7 +67,7 @@ SwitchDisabled.story = {
 
 const ControledSwitch = () => {
   const [checked, onChange] = useState(true);
-  return <Switch checked={checked} onChange={(v, e) => onChange(v)} />;
+  return <Switch checked={checked} onChange={(v, e) => onChange(v)} aria-label='power-switch'/>;
 };
 export const SwitchCheckedOnChange = () => <ControledSwitch />;
 
@@ -82,7 +82,7 @@ const UnControledSwitch = () => {
   return (
     <>
       {/* <Switch onChange={onChange} defaultChecked={false}/> */}
-      <Switch onChange={onChange} defaultChecked={true} />
+      <Switch onChange={onChange} defaultChecked={true} aria-label='power-switch'/>
     </>
   );
 };
@@ -137,15 +137,16 @@ class LoadingDemo extends React.Component {
         <Switch
           checked={this.state.checked}
           onChange={this.onChange}
+          aria-label='power-switch'
           loading={this.state.loading}
         ></Switch>
         <br />
         <br />
         <hr />
-        <Switch loading disabled />
+        <Switch loading disabled aria-label='power-switch'/>
         <br />
         <br />
-        <Switch loading disabled defaultChecked />
+        <Switch loading disabled defaultChecked aria-label='power-switch'/>
         <br />
         <br />
       </>

+ 13 - 13
packages/semi-ui/switch/_story/switch.stories.tsx

@@ -13,9 +13,9 @@ const stories = storiesOf('Switch', module);
 
 stories.add('switch', () => (
     <div>
-        <Switch onChange={(v, e) => console.log(v)}>
+        <Switch onChange={(v, e) => console.log(v)} aria-label='power-switch'>
         </Switch>
-        <Switch defaultChecked={true} onChange={(v, e) => console.log(v)}>
+        <Switch defaultChecked={true} onChange={(v, e) => console.log(v)} aria-label='power-switch'>
         </Switch>
     </div>
 ));
@@ -24,24 +24,24 @@ stories.add('switch', () => (
 stories.add('switch size', () => (
     <div>
         <Switch onChange={(v, e) => console.log(v)}></Switch>
-        <Switch onChange={(v, e) => console.log(v)} size='small'></Switch>
-        <Switch onChange={(v, e) => console.log(v)} size='large'></Switch>
+        <Switch onChange={(v, e) => console.log(v)} size='small' aria-label='power-switch'></Switch>
+        <Switch onChange={(v, e) => console.log(v)} size='large' aria-label='power-switch'></Switch>
     </div>
 ));
 
 stories.add('switch checkedText &  uncheckedText', () => (
     <div>
-        <Switch defaultChecked checkedText='开' uncheckedText='关' />
+        <Switch defaultChecked checkedText='开' uncheckedText='关' aria-label='power-switch'/>
         <Switch checkedText={'|'} uncheckedText='〇' />
         <br/><br/>
         <Switch checkedText='开' uncheckedText='关' />
-        <Switch defaultChecked checkText='|' uncheckedText='〇' />
+        <Switch defaultChecked checkedText='|' uncheckedText='〇' aria-label='power-switch'/>
         <br/><br/>
-        <Switch checkedText='开' uncheckedText='关' size='large' />
-        <Switch checedkText='|' uncheckedText='〇' size='large' />
+        <Switch checkedText='开' uncheckedText='关' size='large' aria-label='power-switch'/>
+        <Switch checkedText='|' uncheckedText='〇' size='large' aria-label='power-switch'/>
         <br/><br/>
-        <Switch defaultChecked checkedText='开' uncheckedText='关' size='large' />
-        <Switch defaultChecked checkedText='|' uncheckedText='〇' size='large' />
+        <Switch defaultChecked checkedText='开' uncheckedText='关' size='large' aria-label='power-switch'/>
+        <Switch defaultChecked checkedText='|' uncheckedText='〇' size='large' aria-label='power-switch'/>
     </div>
 ));
 
@@ -51,7 +51,7 @@ stories.add('switch disabled', () => (
             disabled
         </Switch>
 
-        <Switch disabled checked={true} onChange={(v, e) => console.log(v)}>
+        <Switch disabled checked={true} onChange={(v, e) => console.log(v)} aria-label='power-switch'>
         </Switch>
     </>
 ));
@@ -79,8 +79,8 @@ const UnControledSwitch = () => {
 stories.add('switch defaultChecked + onChange', () => <UnControledSwitch/>);
 
 class LoadingDemo extends React.Component {
-    constructor() {
-        super()
+    constructor(props) {
+        super(props);
         this.state = {
             checked: true,
             loading:false

+ 11 - 3
packages/semi-ui/switch/index.tsx

@@ -9,8 +9,9 @@ import '@douyinfe/semi-foundation/switch/switch.scss';
 
 import { noop } from 'lodash';
 import Spin from '../spin';
-
 export interface SwitchProps {
+    'aria-label'?: string | undefined;
+    'aria-labelledby'?: string | undefined;
     defaultChecked?: boolean;
     checked?: boolean;
     disabled?: boolean;
@@ -23,7 +24,7 @@ export interface SwitchProps {
     size?: 'large' | 'default' | 'small';
     checkedText?: React.ReactNode;
     uncheckedText?: React.ReactNode;
-}
+} 
 
 export interface SwitchState {
     nativeControlChecked: boolean;
@@ -32,6 +33,8 @@ export interface SwitchState {
 
 class Switch extends BaseComponent<SwitchProps, SwitchState> {
     static propTypes = {
+        'aria-label': PropTypes.string,
+        'aria-labelledby': PropTypes.string,
         className: PropTypes.string,
         checked: PropTypes.bool,
         checkedText: PropTypes.node,
@@ -103,6 +106,8 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
     render() {
         const { nativeControlChecked, nativeControlDisabled } = this.state;
         const { className, style, onMouseEnter, onMouseLeave, size, checkedText, uncheckedText, loading } = this.props;
+        const ariaLabel = this.props['aria-label'];
+        const ariaLabelledBy = this.props['aria-labelledby'];
         const wrapperCls = cls(className, {
             [cssClasses.PREFIX]: true,
             [cssClasses.CHECKED]: nativeControlChecked,
@@ -130,7 +135,7 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
                                 size={size === 'default' ? 'middle' : size}
                             />
                         )
-                        : <div className={cssClasses.KNOB} />
+                        : <div className={cssClasses.KNOB} aria-hidden={true} />
                 }
                 {showCheckedText ? (
                     <div className={cssClasses.CHECKED_TEXT}>
@@ -145,6 +150,9 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
                 <input
                     {...switchProps}
                     ref={this.switchRef}
+                    aria-labelledby={ariaLabelledBy}
+                    aria-label={ariaLabel}
+                    aria-checked={nativeControlChecked}
                     onChange={e => this.foundation.handleChange(e.target.checked, e)}
                 />
             </div>