فهرست منبع

chore: merge feat/a11y

走鹃 3 سال پیش
والد
کامیت
c7085c9ecf
53فایلهای تغییر یافته به همراه547 افزوده شده و 76 حذف شده
  1. 1 1
      .github/workflows/lighthouse.yml
  2. 13 4
      packages/semi-foundation/checkbox/checkboxFoundation.ts
  3. 21 2
      packages/semi-foundation/input/foundation.ts
  4. 11 1
      packages/semi-foundation/input/textareaFoundation.ts
  5. 18 0
      packages/semi-foundation/modal/modalContentFoundation.ts
  6. 11 0
      packages/semi-foundation/navigation/itemFoundation.ts
  7. 12 0
      packages/semi-foundation/navigation/subNavFoundation.ts
  8. 8 1
      packages/semi-foundation/radio/radio.scss
  9. 15 1
      packages/semi-foundation/select/foundation.ts
  10. 17 0
      packages/semi-foundation/table/utils.ts
  11. 8 0
      packages/semi-foundation/utils/isEnterPress.ts
  12. 3 0
      packages/semi-foundation/utils/keyCode.ts
  13. 1 1
      packages/semi-icons/src/components/Icon.tsx
  14. 16 0
      packages/semi-ui/_utils/hooks/usePrevFocus.ts
  15. 4 0
      packages/semi-ui/_utils/index.ts
  16. 1 1
      packages/semi-ui/anchor/_story/anchor.stories.js
  17. 4 2
      packages/semi-ui/anchor/index.tsx
  18. 29 4
      packages/semi-ui/anchor/link.tsx
  19. 4 4
      packages/semi-ui/avatar/_story/avatar.stories.js
  20. 1 1
      packages/semi-ui/avatar/avatarGroup.tsx
  21. 10 4
      packages/semi-ui/avatar/index.tsx
  22. 2 0
      packages/semi-ui/button/Button.tsx
  23. 1 1
      packages/semi-ui/button/buttonGroup.tsx
  24. 1 1
      packages/semi-ui/button/splitButtonGroup.tsx
  25. 24 4
      packages/semi-ui/checkbox/checkbox.tsx
  26. 11 4
      packages/semi-ui/checkbox/checkboxGroup.tsx
  27. 7 1
      packages/semi-ui/checkbox/checkboxInner.tsx
  28. 5 5
      packages/semi-ui/empty/index.tsx
  29. 33 2
      packages/semi-ui/input/index.tsx
  30. 9 4
      packages/semi-ui/input/inputGroup.tsx
  31. 25 6
      packages/semi-ui/input/textarea.tsx
  32. 20 1
      packages/semi-ui/inputNumber/index.tsx
  33. 3 1
      packages/semi-ui/modal/Modal.tsx
  34. 35 5
      packages/semi-ui/modal/ModalContent.tsx
  35. 15 0
      packages/semi-ui/navigation/Item.tsx
  36. 13 1
      packages/semi-ui/navigation/SubNav.tsx
  37. 1 1
      packages/semi-ui/navigation/index.tsx
  38. 1 0
      packages/semi-ui/radio/radio.tsx
  39. 1 0
      packages/semi-ui/radio/radioGroup.tsx
  40. 2 0
      packages/semi-ui/radio/radioInner.tsx
  41. 29 2
      packages/semi-ui/select/index.tsx
  42. 3 0
      packages/semi-ui/select/option.tsx
  43. 1 0
      packages/semi-ui/spin/icon.tsx
  44. 25 1
      packages/semi-ui/table/Body/BaseRow.tsx
  45. 7 2
      packages/semi-ui/table/Body/index.tsx
  46. 7 1
      packages/semi-ui/table/ColumnFilter.tsx
  47. 4 2
      packages/semi-ui/table/ColumnSelection.tsx
  48. 18 1
      packages/semi-ui/table/ColumnSorter.tsx
  49. 5 0
      packages/semi-ui/table/CustomExpandIcon.tsx
  50. 2 0
      packages/semi-ui/table/Table.tsx
  51. 11 1
      packages/semi-ui/table/TableCell.tsx
  52. 16 2
      packages/semi-ui/table/TableHeaderRow.tsx
  53. 2 0
      packages/semi-ui/toast/toast.tsx

+ 1 - 1
.github/workflows/lighthouse.yml

@@ -4,7 +4,7 @@ on:
   pull_request:
     branches: [ main, release, milestone** ]
   push:
-    branches: [ main, release, milestone**, feat/a11y-ci ]
+    branches: [ main, release, milestone**, feat/a11y-aria ]
 
 jobs:
   lhci:

+ 13 - 4
packages/semi-foundation/checkbox/checkboxFoundation.ts

@@ -1,4 +1,5 @@
 import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface BasicTargetObject {
     [x: string]: any;
@@ -28,7 +29,7 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     init() {}
 
-    getEvent(checked: boolean, e: MouseEvent) {
+    getEvent(checked: boolean, e: any) {
         const props = this.getProps();
         const cbValue = {
             target: {
@@ -45,12 +46,12 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         return cbValue;
     }
 
-    notifyChange(checked: boolean, e: MouseEvent) {
+    notifyChange(checked: boolean, e: any) {
         const cbValue = this.getEvent(checked, e);
         this._adapter.notifyChange(cbValue);
     }
 
-    handleChange(e: MouseEvent) {
+    handleChange(e: any) {
         const disabled = this.getProp('disabled');
 
         if (disabled) {
@@ -78,7 +79,7 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         }
     }
 
-    handleChangeInGroup(e: MouseEvent) {
+    handleChangeInGroup(e: any) {
         const { value } = this.getProps();
         const groupValue = this._adapter.getGroupValue();
         const checked = groupValue.includes(value);
@@ -88,6 +89,12 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         this._adapter.notifyGroupChange(event);
     }
 
+    handleEnterPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleChange(e);
+        }
+    }
+
     setChecked(checked: boolean) {
         this._adapter.setNativeControlChecked(checked);
     }
@@ -110,6 +117,8 @@ export interface BaseCheckboxProps {
     onMouseEnter?: (e: any) => void;
     onMouseLeave?: (e: any) => void;
     extra?: any;
+    addonId?: string;
+    extraId?: string;
 }
 
 export default CheckboxFoundation;

+ 21 - 2
packages/semi-foundation/input/foundation.ts

@@ -2,7 +2,8 @@ import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation
 import { strings } from './constants';
 import { noop, set, isNumber, isString, isFunction } from 'lodash';
 
-const ENTER_KEY_CODE = 'Enter';
+import isEnterPress from '../utils/isEnterPress';
+import { ENTER_KEY } from './../utils/keyCode';
 
 export interface InputDefaultAdapter {
     notifyChange: noopFunction;
@@ -261,7 +262,7 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
 
     handleKeyPress(e: any) {
         this._adapter.notifyKeyPress(e);
-        if (e.key === ENTER_KEY_CODE) {
+        if (e.key === ENTER_KEY) {
             this._adapter.notifyEnterPress(e);
         }
     }
@@ -294,5 +295,23 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
             e.preventDefault();
         }
     }
+
+    /**
+     * A11y: simulate clear button click
+     */
+    handleClearEnterPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleClear(e);
+        }
+    }
+
+    /**
+     * A11y: simulate password button click
+     */
+    handleModeEnterPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleClickEye(e);
+        }
+    }
 }
 export default InputFoundation;

+ 11 - 1
packages/semi-foundation/input/textareaFoundation.ts

@@ -7,6 +7,7 @@ import {
 } from 'lodash';
 import calculateNodeHeight from './util/calculateNodeHeight';
 import getSizingData from './util/getSizingData';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface TextAreaDefaultAdpter {
     notifyChange: noopFunction;
@@ -171,7 +172,7 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdpter> {
         }
     }
 
-    resizeTextarea = (cb: any) => {
+    resizeTextarea = (cb?: any) => {
         const { height } = this.getStates();
         const { rows } = this.getProps();
         const node = this._adapter.getRef().current;
@@ -232,4 +233,13 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdpter> {
         this._adapter.notifyClear(e);
         this.stopPropagation(e);
     }
+
+    /**
+     * A11y: simulate clear button click
+     */
+    handleClearEnterPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleClear(e);
+        }
+    }
 }

+ 18 - 0
packages/semi-foundation/modal/modalContentFoundation.ts

@@ -13,6 +13,7 @@ export interface ModalContentProps extends ModalProps {
 
 export interface ModalContentState {
     dialogMouseDown: boolean;
+    prevFocusElement: HTMLElement;
 }
 
 export interface ModalContentAdapter extends DefaultAdapter<ModalContentProps, ModalContentState> {
@@ -22,6 +23,9 @@ export interface ModalContentAdapter extends DefaultAdapter<ModalContentProps, M
     addKeyDownEventListener: () => void;
     removeKeyDownEventListener: () => void;
     getMouseState: () => boolean;
+    modalDialogFocus: () => void;
+    modalDialogBlur: () => void;
+    prevFocusElementReFocus: () => void;
 }
 
 export default class ModalContentFoundation extends BaseFoundation<ModalContentAdapter> {
@@ -32,6 +36,8 @@ export default class ModalContentFoundation extends BaseFoundation<ModalContentA
 
     destroy() {
         this.handleKeyDownEventListenerUnmount();
+        this.modalDialogBlur();
+        this.prevFocusElementReFocus();
     }
 
     handleDialogMouseDown() {
@@ -73,4 +79,16 @@ export default class ModalContentFoundation extends BaseFoundation<ModalContentA
     close(e: any) {
         this._adapter.notifyClose(e);
     }
+
+    modalDialogFocus() {
+        this._adapter.modalDialogFocus();
+    }
+
+    modalDialogBlur() {
+        this._adapter.modalDialogBlur();
+    }
+
+    prevFocusElementReFocus() {
+        this._adapter.prevFocusElementReFocus();
+    }
 }

+ 11 - 0
packages/semi-foundation/navigation/itemFoundation.ts

@@ -1,5 +1,6 @@
 /* argus-disable unPkgSensitiveInfo */
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface ItemProps {
     text?: any;
@@ -37,6 +38,7 @@ export interface ItemAdapter<P = Record<string, any>, S = Record<string, any>> e
     notifyMouseLeave(e: any): void;
     getIsCollapsed(): boolean;
     getSelected(): boolean;
+    getIsOpen(): boolean;
 }
 
 export default class ItemFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<ItemAdapter<P, S>, P, S> {
@@ -91,4 +93,13 @@ export default class ItemFoundation<P = Record<string, any>, S = Record<string,
         }
         this._adapter.notifyClick({ itemKey, text, domEvent: e });
     }
+
+    /**
+     * A11y: simulate item click
+     */
+    handleKeyPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleClick(e);
+        }
+    }
 }

+ 12 - 0
packages/semi-foundation/navigation/subNavFoundation.ts

@@ -1,4 +1,5 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import isEnterPress from '../utils/isEnterPress';
 
 const addKeys = function addKeys(originKeys: (string | number)[] = [], ...willAddKeys: (string | number)[]) {
     const keySet = new Set(originKeys);
@@ -120,4 +121,15 @@ export default class SubNavFoundation<P = Record<string, any>, S = Record<string
         this._adapter.notifyGlobalOpenChange(cbVal);
         this._adapter.notifyGlobalOnClick(cbVal);
     }
+
+    /**
+     * A11y: simulate sub nav click
+     * @param e 
+     * @param titleRef 
+     */
+    handleKeyPress(e: any, titleRef: any) {
+        if (isEnterPress(e)) {
+            this.handleClick(e, titleRef);
+        }
+    }
 }

+ 8 - 1
packages/semi-foundation/radio/radio.scss

@@ -275,9 +275,16 @@ $inner-width: $width-icon-medium;
         }
     }
 
+    // A11y: arrow keyboard control
     .#{$module}-inner-buttonRadio,
     .#{$module}-inner-pureCardRadio  {
-        display: none;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        z-index: -1;
+        opacity: 0;
     }
 
     &-disabled {

+ 15 - 1
packages/semi-foundation/select/foundation.ts

@@ -1,11 +1,12 @@
 /* argus-disable unPkgSensitiveInfo */
 /* eslint-disable max-len */
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
-import KeyCode from '../utils/keyCode';
 import { isNumber, isString, isEqual, omit } from 'lodash';
+import KeyCode, { ENTER_KEY } from '../utils/keyCode';
 import warning from '../utils/warning';
 import isNullOrUndefined from '../utils/isNullOrUndefined';
 import { BasicOptionProps } from './optionFoundation';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface SelectAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     getTriggerWidth(): number;
@@ -650,6 +651,7 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
                 this._handleEnterKeyDown(event);
                 break;
             case KeyCode.ESC:
+            case KeyCode.TAB:
                 this.close(event);
                 break;
             default:
@@ -869,6 +871,18 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
         e.stopPropagation();
     }
 
+    handleKeyPress(e: KeyboardEvent) {
+        if (e && e.key === ENTER_KEY) {
+            this.handleClick(e);
+        }
+    }
+
+    handleClearBtnEnterPress(e: KeyboardEvent) {
+        if (isEnterPress(e)) {
+            this.handleClearClick(e as any);
+        }
+    }
+
     handleOptionMouseEnter(optionIndex: number) {
         this._adapter.updateFocusIndex(optionIndex);
     }

+ 17 - 0
packages/semi-foundation/table/utils.ts

@@ -484,4 +484,21 @@ export function warnIfNoDataIndex(column: Record<string, any>) {
             logger.warn(`The column with sorter or filter must pass the 'dataIndex' prop`);
         }
     }
+}
+
+/**
+ * Whether is tree table
+ */
+export function isTreeTable({ dataSource, childrenRecordName = 'children' }: { dataSource: Record<string, any>; childrenRecordName?: string; }) {
+    let flag = false;
+    if (Array.isArray(dataSource)) {
+        for (const data of dataSource) {
+            const children = get(data, childrenRecordName);
+            if (Array.isArray(children) && children.length) {
+                flag = true;
+                break;
+            }
+        }
+    }
+    return flag;
 }

+ 8 - 0
packages/semi-foundation/utils/isEnterPress.ts

@@ -0,0 +1,8 @@
+import { get } from 'lodash-es';
+import { ENTER_KEY } from './keyCode';
+
+function isEnterPress<T extends { key: string }>(e: T) {
+    return get(e, 'key') === ENTER_KEY ? true : false;
+}
+
+export default isEnterPress;

+ 3 - 0
packages/semi-foundation/utils/keyCode.ts

@@ -426,4 +426,7 @@ const keyCode = {
     WIN_IME: 229,
 };
 
+export const ENTER_KEY = 'Enter';
+export const TAB_KEY = 'Tab';
+
 export default keyCode;

+ 1 - 1
packages/semi-icons/src/components/Icon.tsx

@@ -31,7 +31,7 @@ const Icon = React.forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
         outerStyle.transform = `rotate(${rotate}deg)`;
     }
     Object.assign(outerStyle, style);
-    return <span role="img" ref={ref} className={classes} style={outerStyle} {...restProps}>{svg}</span>;
+    return <span role="img" ref={ref} aria-label={type} className={classes} style={outerStyle} {...restProps}>{svg}</span>;
 });
 
 // @ts-ignore used to judge whether it is a semi-icon in semi-ui

+ 16 - 0
packages/semi-ui/_utils/hooks/usePrevFocus.ts

@@ -0,0 +1,16 @@
+import { useState, useEffect } from 'react';
+import { getActiveElement } from '../index';
+import { get, isFunction } from 'lodash-es';
+
+export function usePrevFocus() {
+    const [prevFocusElement, setPrevFocus] = useState<HTMLElement>(getActiveElement());
+
+    useEffect(() => {
+        return function cleanup() {
+            const blur = get(prevFocusElement, 'blur');
+            isFunction(blur) && blur();
+        };
+    }, [prevFocusElement]);
+
+    return [prevFocusElement, setPrevFocus];
+}

+ 4 - 0
packages/semi-ui/_utils/index.ts

@@ -155,3 +155,7 @@ export interface HighLightTextHTMLChunk {
  * @returns boolean
  */
 export const isSemiIcon = (icon: any): boolean => React.isValidElement(icon) && get(icon.type, 'elementType') === 'Icon';
+
+export function getActiveElement() {
+    return document ? document.activeElement as HTMLElement : null;
+}

+ 1 - 1
packages/semi-ui/anchor/_story/anchor.stories.js

@@ -15,7 +15,7 @@ const Link = Anchor.Link;
 export const Size = () => (
   <div>
     <div>小号尺寸</div>
-    <Anchor size={'small'}>
+    <Anchor ariaLabel="小号尺寸" size={'small'}>
       <Link href="#welcome" title="welcome" />
       <Link href="#api" title="api too much to show" />
       <Link href="#contact" title="contact" />

+ 4 - 2
packages/semi-ui/anchor/index.tsx

@@ -31,6 +31,7 @@ export interface AnchorProps {
     targetOffset?: number;
     onChange?: (currentLink: string, previousLink: string) => void;
     onClick?: (e: React.MouseEvent<HTMLElement>, currentLink: string) => void;
+    ariaLabel?: string;
 }
 
 export interface AnchorState {
@@ -250,6 +251,7 @@ class Anchor extends BaseComponent<AnchorProps, AnchorState> {
             showTooltip,
             position,
             autoCollapse,
+            ariaLabel
         } = this.props;
         const { activeLink, scrollHeight, slideBarTop } = this.state;
         const wrapperCls = cls(prefixCls, className, {
@@ -282,8 +284,8 @@ class Anchor extends BaseComponent<AnchorProps, AnchorState> {
                     removeLink: this.removeLink,
                 }}
             >
-                <div className={wrapperCls} style={wrapperStyle} id={this.anchorID}>
-                    <div className={slideCls} style={{ height: scrollHeight }}>
+                <div role="navigation" aria-label={ ariaLabel || 'Side navigation'} className={wrapperCls} style={wrapperStyle} id={this.anchorID}>
+                    <div aria-hidden className={slideCls} style={{ height: scrollHeight }}>
                         <span className={slideBarCls} style={{ top: slideBarTop }} />
                     </div>
                     <div className={anchorWrapper}>{children}</div>

+ 29 - 4
packages/semi-ui/anchor/link.tsx

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import BaseComponent from '../_base/baseComponent';
 import { cssClasses } from '@douyinfe/semi-foundation/anchor/constants';
 import LinkFoundation, { LinkAdapter } from '@douyinfe/semi-foundation/anchor/linkFoundation';
-import AnchorContext from './anchor-context';
+import AnchorContext, { AnchorContextType } from './anchor-context';
 import Typography from '../typography/index';
 
 const prefixCls = cssClasses.PREFIX;
@@ -36,9 +36,11 @@ export default class Link extends BaseComponent<LinkProps, {}> {
 
     foundation: LinkFoundation;
 
+    context!: AnchorContextType;
     constructor(props: LinkProps) {
         super(props);
         this.foundation = new LinkFoundation(this.adapter);
+        this.handleClick = this.handleClick.bind(this);
     }
 
     get adapter(): LinkAdapter {
@@ -65,6 +67,12 @@ export default class Link extends BaseComponent<LinkProps, {}> {
         this.foundation.handleUpdateLink(href, prevHref);
     }
 
+    handleClick(e: React.KeyboardEvent | React.MouseEvent) {
+        const { disabled, href } = this.props;
+        const { onClick } = this.context;
+        !disabled && onClick(e as any, href);
+    }
+
     componentDidMount() {
         this.handleAddLink();
     }
@@ -115,18 +123,35 @@ export default class Link extends BaseComponent<LinkProps, {}> {
     };
 
     render() {
-        const { href, className, style, disabled = false } = this.props;
-        const { activeLink, onClick } = this.context;
+        const { href, className, style, disabled = false, title } = this.props;
+        const { activeLink, showTooltip } = this.context;
         const active = activeLink === href;
         const linkCls = cls(`${prefixCls}-link`, className);
         const linkTitleCls = cls(`${prefixCls}-link-title`, {
             [`${prefixCls}-link-title-active`]: active,
             [`${prefixCls}-link-title-disabled`]: disabled,
         });
+        const ariaProps = {
+            'aria-disabled': disabled,
+            'aria-label': href,
+        };
+        if (active) {
+            ariaProps['aria-details'] = 'active';
+        }
+        if (!showTooltip && typeof title === 'string') {
+            ariaProps['title'] = title;
+        }
 
         return (
             <div className={linkCls} style={style}>
-                <div className={linkTitleCls} onClick={e => !disabled && onClick(e, href)}>
+                <div
+                    role="link"
+                    tabIndex={0}
+                    {...ariaProps}
+                    className={linkTitleCls}
+                    onClick={e => this.handleClick(e)}
+                    onKeyPress={e => this.handleClick(e)}
+                >
                     {this.renderTitle()}
                 </div>
                 {this.renderChildren()}

+ 4 - 4
packages/semi-ui/avatar/_story/avatar.stories.js

@@ -11,7 +11,7 @@ export const Basic = () => (
   <div>
     <div>
       <Avatar>U</Avatar>
-      <Avatar size="large">U</Avatar>
+      <Avatar autoFocus size="large" onClick={() => console.log('ok')}>U</Avatar>
       <Avatar size="extra-small">U</Avatar>
       <Avatar size="small">U</Avatar>
       <Avatar size="default">U</Avatar>
@@ -45,9 +45,9 @@ export const CustomAvatar = () => (
     <Avatar color="red" size="default">
       DF
     </Avatar>
-    <Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
-    <Avatar size="default" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
-    <Avatar size="small" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
+    <Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" alt="a man" />
+    <Avatar size="default" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" alt="a man" />
+    <Avatar size="small" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" alt="a man" />
     <Avatar style={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>U</Avatar>
     <Avatar style={{ backgroundColor: '#87d068' }}>YZ</Avatar>
   </div>

+ 1 - 1
packages/semi-ui/avatar/avatarGroup.tsx

@@ -76,6 +76,6 @@ export default class AvatarGroup extends PureComponent<AvatarGroupProps> {
 
         }
 
-        return <div className={groupCls}>{inner}</div>;
+        return <div className={groupCls} aria-label="avatar group">{inner}</div>;
     }
 }

+ 10 - 4
packages/semi-ui/avatar/index.tsx

@@ -47,6 +47,7 @@ export default class Avatar extends BaseComponent<AvatarProps, AvatarState> {
         onMouseLeave: PropTypes.func,
     };
 
+    foundation!: AvatarFoundation;
     constructor(props: AvatarProps) {
         super(props);
         this.state = {
@@ -106,12 +107,12 @@ export default class Avatar extends BaseComponent<AvatarProps, AvatarState> {
         this.foundation.destroy();
     }
 
-    onEnter() {
-        this.foundation.handleEnter();
+    onEnter(e: React.MouseEvent) {
+        this.foundation.handleEnter(e);
     }
 
-    onLeave() {
-        this.foundation.handleLeave();
+    onLeave(e: React.MouseEvent) {
+        this.foundation.handleLeave(e);
     }
 
     handleError() {
@@ -135,19 +136,24 @@ export default class Avatar extends BaseComponent<AvatarProps, AvatarState> {
         );
         let content = children;
         const hoverRender = hoverContent ? (<div className={`${prefixCls}-hover`}>{hoverContent}</div>) : null;
+        let ariaLabel;
         if (isImg) {
             content = (
                 <img src={src} srcSet={srcSet} onError={this.handleError} alt={alt} {...imgAttr} />
             );
+            ariaLabel = 'avatar';
         } else if (typeof children === 'string') {
             content = (
                 <span className={`${prefixCls}-content`}>
                     <span className={`${prefixCls}-label`}>{children}</span>
                 </span>
             );
+            ariaLabel = `avatar of ${children}`;
         }
         return (
+            // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
             <span
+                aria-label={ariaLabel}
                 {...(others as any)}
                 style={style}
                 className={avatarCls}

+ 2 - 0
packages/semi-ui/button/Button.tsx

@@ -106,6 +106,7 @@ export default class Button extends PureComponent<ButtonProps> {
                 className
             ),
             type: htmlType,
+            'aria-disabled': disabled,
         };
 
         return (
@@ -116,6 +117,7 @@ export default class Button extends PureComponent<ButtonProps> {
                 onMouseDown={this.props.onMouseDown}
                 style={style}
             >
+                {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
                 <span className={`${prefixCls}-content`} onClick={e => disabled && e.stopPropagation()}>
                     {children}
                 </span>

+ 1 - 1
packages/semi-ui/button/buttonGroup.tsx

@@ -42,6 +42,6 @@ export default class ButtonGroup extends BaseComponent<ButtonGroupProps> {
                 React.cloneElement(itm, { disabled, size, type, ...itm.props, ...rest, key: index })
             );
         }
-        return <div className={`${prefixCls}-group`}>{inner}</div>;
+        return <div className={`${prefixCls}-group`} role="group" aria-label="button group">{inner}</div>;
     }
 }

+ 1 - 1
packages/semi-ui/button/splitButtonGroup.tsx

@@ -19,7 +19,7 @@ export default class SplitButtonGroup extends BaseComponent<SplitButtonGroupProp
         const { children, style, className } = this.props;
         const cls = classNames(`${prefixCls}-split`, className);
         return (
-            <div className={cls} style={style}>
+            <div className={cls} style={style} role="group" aria-label="split style button group">
                 {children}
             </div>
         );

+ 24 - 4
packages/semi-ui/checkbox/checkbox.tsx

@@ -19,6 +19,7 @@ export interface CheckboxProps extends BaseCheckboxProps {
     onMouseEnter?: React.MouseEventHandler<HTMLSpanElement>;
     onMouseLeave?: React.MouseEventHandler<HTMLSpanElement>;
     extra?: React.ReactNode;
+    ariaLabel?: string;
 }
 interface CheckboxState {
     checked: boolean;
@@ -44,6 +45,10 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
         onMouseEnter: PropTypes.func,
         onMouseLeave: PropTypes.func,
         extra: PropTypes.node,
+        addonId: PropTypes.string, // A11y aria-labelledby
+        extraId: PropTypes.string, // A11y aria-describedby
+        index: PropTypes.number,
+        ariaLabel: PropTypes.string,
     };
 
     static defaultProps = {
@@ -74,6 +79,7 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
         };
     }
 
+    foundation: CheckboxFoundation;
     constructor(props: CheckboxProps) {
         super(props);
 
@@ -111,6 +117,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
 
     handleChange: React.MouseEventHandler<HTMLSpanElement> = e => this.foundation.handleChange(e);
 
+    handleEnterPress = (e: React.KeyboardEvent<HTMLSpanElement>) => this.foundation.handleEnterPress(e);
+
     render() {
         const {
             disabled,
@@ -123,6 +131,9 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             onMouseLeave,
             extra,
             value,
+            addonId,
+            extraId,
+            ariaLabel
         } = this.props;
         const { checked } = this.state;
         const props: Record<string, any> = {
@@ -130,7 +141,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             disabled,
         };
 
-        if (this.isInGroup()) {
+        const inGroup = this.isInGroup();
+        if (inGroup) {
             if (this.context.checkboxGroup.value) {
                 const realChecked = (this.context.checkboxGroup.value || []).includes(value);
                 props.checked = realChecked;
@@ -161,21 +173,29 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             [`${prefix}-cardType_extra_noChildren`]: props.isCardType && !children,
         });
 
-        const name = this.isInGroup() && this.context.checkboxGroup.name;
+        const name = inGroup && this.context.checkboxGroup.name;
 
         const renderContent = () => (
             <>
-                {children ? <span className={`${prefix}-addon`}>{children}</span> : null}
-                {extra ? <div className={extraCls}>{extra}</div> : null}
+                {children ? <span id={addonId} className={`${prefix}-addon`}>{children}</span> : null}
+                {extra ? <div id={extraId} className={extraCls}>{extra}</div> : null}
             </>
         );
         return (
             <span
+                role='checkbox'
+                tabIndex={disabled ? -1 : 0}
+                aria-label={ariaLabel}
+                aria-disabled={props.checked}
+                aria-checked={props.checked}
+                aria-labelledby={addonId}
+                aria-describedby={extraId}
                 style={style}
                 className={wrapper}
                 onMouseEnter={onMouseEnter}
                 onMouseLeave={onMouseLeave}
                 onClick={this.handleChange}
+                onKeyPress={this.handleEnterPress}
             >
                 <CheckboxInner
                     {...this.props}

+ 11 - 4
packages/semi-ui/checkbox/checkboxGroup.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import classnames from 'classnames';
 import { checkboxGroupClasses as css, strings } from '@douyinfe/semi-foundation/checkbox/constants';
-import CheckboxGroupFoudation, { CheckboxGroupAdapter } from '@douyinfe/semi-foundation/checkbox/checkboxGroupFoundation';
+import CheckboxGroupFoundation, { CheckboxGroupAdapter } from '@douyinfe/semi-foundation/checkbox/checkboxGroupFoundation';
 import BaseComponent from '../_base/baseComponent';
 import { Context } from './context';
 import { isEqual } from 'lodash';
@@ -68,12 +68,13 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
         };
     }
 
+    foundation: CheckboxGroupFoundation;
     constructor(props: CheckboxGroupProps) {
         super(props);
         this.state = {
             value: props.value || props.defaultValue,
         };
-        this.foundation = new CheckboxGroupFoudation(this.adapter);
+        this.foundation = new CheckboxGroupFoundation(this.adapter);
         this.onChange = this.onChange.bind(this);
     }
 
@@ -96,7 +97,7 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
     }
 
     render() {
-        const { children, options, prefixCls, direction, className, style, type } = this.props;
+        const { children, options, prefixCls, direction, className, style, type, disabled } = this.props;
 
         const isPureCardType = type === strings.TYPE_PURECARD;
         const isCardType = type === strings.TYPE_CARD || isPureCardType;
@@ -148,7 +149,13 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
         }
 
         return (
-            <div className={prefixClsDisplay} style={style}>
+            <div
+                role="listbox"
+                aria-label="Checkbox group"
+                aria-disabled={disabled}
+                className={prefixClsDisplay} 
+                style={style}
+            >
                 <Context.Provider
                     value={{
                         checkboxGroup: {

+ 7 - 1
packages/semi-ui/checkbox/checkboxInner.tsx

@@ -15,6 +15,8 @@ export interface CheckboxInnerProps {
     name?: string;
     isPureCardType?: boolean;
     ref?: React.MutableRefObject<CheckboxInner> | ((ref: CheckboxInner) => void);
+    addonId?: string;
+    extraId?: string;
 }
 
 class CheckboxInner extends PureComponent<CheckboxInnerProps> {
@@ -28,6 +30,8 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
         grouped: PropTypes.bool,
         value: PropTypes.any,
         isPureCardType: PropTypes.bool,
+        addonId: PropTypes.string,
+        extraId: PropTypes.string,
     };
 
     static defaultProps = {
@@ -44,7 +48,7 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
     }
 
     render() {
-        const { indeterminate, checked, disabled, prefixCls, name, isPureCardType } = this.props;
+        const { indeterminate, checked, disabled, prefixCls, name, isPureCardType, addonId, extraId } = this.props;
         const prefix = prefixCls || css.PREFIX;
 
         const wrapper = classnames(
@@ -69,6 +73,8 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
         return (
             <span className={wrapper}>
                 <input
+                    aria-hidden={true}
+                    tabIndex={-1}
                     ref={ref => {
                         this.inputEntity = ref;
                     }}

+ 5 - 5
packages/semi-ui/empty/index.tsx

@@ -108,17 +108,17 @@ export default class Empty extends BaseComponent<EmptyProps, EmptyState> {
                 style: { fontWeight: 400 },
             };
         return (
-            <div className={wrapperCls} style={style}>
-                <div className={`${prefixCls}-image`} style={imageStyle}>
+            <div className={wrapperCls} style={style} aria-label="empty placeholder image">
+                <div className={`${prefixCls}-image`} style={imageStyle} >
                     {imageNode}
                 </div>
-                <div className={`${prefixCls}-content`}>
+                <div className={`${prefixCls}-content`} >
                     {title ? (
-                        <Typography.Title {...(titleProps as any)} className={`${prefixCls}-title`}>
+                        <Typography.Title {...(titleProps as any)} className={`${prefixCls}-title`} >
                             {title}
                         </Typography.Title>
                     ) : null}
-                    {description ? <div className={`${prefixCls}-description`}>{description}</div> : null}
+                    {description ? <div className={`${prefixCls}-description`} >{description}</div> : null}
                     {children ? <div className={`${prefixCls}-footer`}>{children}</div> : null}
                 </div>
             </div>

+ 33 - 2
packages/semi-ui/input/index.tsx

@@ -207,6 +207,10 @@ class Input extends BaseComponent<InputProps, InputState> {
         this.foundation.handleClear(e);
     };
 
+    handleClearEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+        this.foundation.handleClearEnterPress(e);
+    };
+
     handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
         this.foundation.handleClick(e);
     };
@@ -235,6 +239,10 @@ class Input extends BaseComponent<InputProps, InputState> {
         this.foundation.handleMouseUp(e);
     };
 
+    handleModeEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+        this.foundation.handleModeEnterPress(e);
+    }
+
     handleClickPrefixOrSuffix = (e: React.MouseEvent<HTMLInputElement>) => {
         this.foundation.handleClickPrefixOrSuffix(e);
     };
@@ -275,7 +283,14 @@ class Input extends BaseComponent<InputProps, InputState> {
         // use onMouseDown to fix issue 1203
         if (allowClear) {
             return (
-                <div className={clearCls} onMouseDown={this.handleClear}>
+                <div
+                    role="button"
+                    tabIndex={0}
+                    aria-label="Clear input value"
+                    className={clearCls}
+                    onMouseDown={this.handleClear}
+                    onKeyPress={this.handleClearEnterPress}
+                >
                     <IconClear />
                 </div>
             );
@@ -289,9 +304,19 @@ class Input extends BaseComponent<InputProps, InputState> {
         const modeCls = cls(`${prefixCls}-modebtn`);
         const modeIcon = eyeClosed ? <IconEyeClosedSolid /> : <IconEyeOpened />;
         const showModeBtn = mode === 'password' && value && !disabled && (isFocus || isHovering);
+        const ariaLabel = eyeClosed ? 'Show password' : 'Hidden password';
         if (showModeBtn) {
             return (
-                <div className={modeCls} onClick={this.handleClickEye} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
+                <div
+                    role="button"
+                    tabIndex={0}
+                    aria-label={ariaLabel}
+                    className={modeCls}
+                    onClick={this.handleClickEye}
+                    onMouseDown={this.handleMouseDown}
+                    onMouseUp={this.handleMouseUp}
+                    onKeyPress={this.handleModeEnterPress}
+                >
                     {modeIcon}
                 </div>
             );
@@ -312,6 +337,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),
         });
 
+        // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
         return <div className={prefixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix}>{labelNode}</div>;
     }
 
@@ -332,6 +358,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             [`${prefixCls }-suffix-icon`]: isSemiIcon(suffix),
             [`${prefixCls}-suffix-hidden`]: suffixAllowClear && Boolean(hideSuffix),
         });
+        // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
         return <div className={suffixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix}>{suffix}</div>;
     }
 
@@ -417,7 +444,11 @@ class Input extends BaseComponent<InputProps, InputState> {
         if (stateMinLength) {
             inputProps.minLength = stateMinLength;
         }
+        if (validateStatus === 'error') {
+            inputProps['aria-invalid'] = "true";
+        }
         return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
             <div
                 className={wrapperCls}
                 style={style}

+ 9 - 4
packages/semi-ui/input/inputGroup.tsx

@@ -4,7 +4,7 @@ import cls from 'classnames';
 import PropTypes from 'prop-types';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/input/constants';
 import BaseComponent from '../_base/baseComponent';
-import Label from '../form/label';
+import Label, { LabelProps } from '../form/label';
 
 import { noop } from '@douyinfe/semi-foundation/utils/function';
 import { isFunction } from 'lodash';
@@ -21,7 +21,7 @@ export interface InputGroupProps {
     style?: Record<string, any>;
     onBlur?: (e: React.FocusEvent<HTMLSpanElement>) => void;
     onFocus?: (e: React.FocusEvent<HTMLSpanElement>) => void;
-    label?: Record<string, any>;
+    label?: LabelProps;
     labelPosition?: string;
     disabled?: boolean;
 }
@@ -64,10 +64,12 @@ export default class inputGroup extends BaseComponent<InputGroupProps, InputGrou
             }
         );
         // const labelCls = cls(label.className, '');
+        const defaultName = 'input-group';
         return (
-            <div className={groupWrapperCls}>
-                {label && label.text ? <Label {...label} /> : null}
+            <div role="group" aria-label="Input group" aria-disabled={this.props.disabled} className={groupWrapperCls}>
+                {label && label.text ? <Label name={defaultName} {...label} /> : null}
                 <span
+                    id={label && label.name || defaultName}
                     className={groupCls}
                     style={this.props.style}
                     onFocus={this.props.onFocus}
@@ -107,6 +109,9 @@ export default class inputGroup extends BaseComponent<InputGroupProps, InputGrou
 
         return (
             <span
+                role="group"
+                aria-label="Input group"
+                aria-disabled={this.props.disabled}
                 className={groupCls}
                 style={style}
                 onFocus={this.props.onFocus}

+ 25 - 6
packages/semi-ui/input/textarea.tsx

@@ -101,6 +101,7 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
     libRef: React.RefObject<React.ReactNode>;
     _resizeLock: boolean;
     _resizeListener: any;
+    foundation: TextAreaFoundation;
 
     constructor(props: TextAreaProps) {
         super(props);
@@ -197,6 +198,10 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         this.foundation.handleClear(e);
     };
 
+    handleClearEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+        this.foundation.handleClearEnterPress(e);
+    }
+
     renderClearBtn() {
         const { showClear } = this.props;
         const displayClearBtn = this.foundation.isAllowClear();
@@ -205,7 +210,14 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         });
         if (showClear) {
             return (
-                <div className={clearCls} onClick={this.handleClear}>
+                <div
+                    role="button"
+                    tabIndex={0}
+                    aria-label="Clear textarea value"
+                    className={clearCls}
+                    onClick={this.handleClear}
+                    onKeyPress={this.handleClearEnterPress}
+                >
                     <IconClear />
                 </div>
             );
@@ -214,10 +226,10 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
     }
 
     renderCounter() {
-        let counter,
-            current,
-            total,
-            countCls;
+        let counter: React.ReactNode,
+            current: number,
+            total: number,
+            countCls: string;
         const { showCounter, maxCount, getValueLength } = this.props;
         if (showCounter || maxCount) {
             const { value } = this.state;
@@ -231,7 +243,14 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
                 }
             );
             counter = (
-                <div className={countCls}>{current}{total ? '/' : null}{total}</div>
+                <div
+                    aria-label="Textarea value length counter"
+                    aria-valuemax={maxCount}
+                    aria-valuenow={current}
+                    className={countCls}
+                >
+                    {current}{total ? '/' : null}{total}
+                </div>
             );
         } else {
             counter = null;

+ 20 - 1
packages/semi-ui/inputNumber/index.tsx

@@ -377,6 +377,8 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
         return (
             <div className={suffixChildrenCls}>
                 <span
+                    role="button"
+                    tabIndex={-1}
                     className={upClassName}
                     onMouseDown={notAllowedUp ? noop : this.handleUpClick}
                     onMouseUp={this.handleMouseUp}
@@ -385,6 +387,8 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                     <IconChevronUp size="extra-small" />
                 </span>
                 <span
+                    role="button"
+                    tabIndex={-1}
                     className={downClassName}
                     onMouseDown={notAllowedDown ? noop : this.handleDownClick}
                     onMouseUp={this.handleMouseUp}
@@ -433,13 +437,26 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
             keepFocus,
             ...rest
         } = this.props;
-        const { value } = this.state;
+        const { value, number } = this.state;
 
         const inputNumberCls = classnames(className, `${prefixCls}-number`, {
             [`${prefixCls}-number-size-${size}`]: size,
         });
 
         const buttons = this.renderButtons();
+        const ariaProps = {
+            'aria-disabled': disabled,
+            step,
+        };
+        if (number) {
+            ariaProps['aria-valuenow'] = number;
+        }
+        if (max !== Infinity) {
+            ariaProps['aria-valuemax'] = max;
+        }
+        if (min !== -Infinity) {
+            ariaProps['aria-valuemin'] = min;
+        }
 
         const input = (
             <div
@@ -450,6 +467,8 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                 onMouseLeave={e => this.handleInputMouseLeave(e)}
             >
                 <Input
+                    role="spinbutton"
+                    {...ariaProps}
                     {...rest}
                     size={size}
                     disabled={disabled}

+ 3 - 1
packages/semi-ui/modal/Modal.tsx

@@ -264,7 +264,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
         if (!visible && !hidden) {
             this.foundation.toggleHidden(true, () => this.foundation.afterClose());
         } else if (visible && this.state.hidden) {
-            this.foundation.toggleHidden(false)
+            this.foundation.toggleHidden(false);
         }
     }
 
@@ -283,6 +283,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
             } else {
                 return (
                     <Button
+                        aria-label="cancel"
                         onClick={this.handleCancel}
                         loading={cancelLoading}
                         type="tertiary"
@@ -300,6 +301,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
                     <div>
                         {getCancelButton(locale)}
                         <Button
+                            aria-label="confirm"
                             type={okType}
                             theme="solid"
                             loading={confirmLoading}

+ 35 - 5
packages/semi-ui/modal/ModalContent.tsx

@@ -13,8 +13,9 @@ import ModalContentFoundation, {
     ModalContentProps,
     ModalContentState
 } from '@douyinfe/semi-foundation/modal/modalContentFoundation';
-import { noop } from 'lodash';
+import { noop, isFunction, get } from 'lodash';
 import { IconClose } from '@douyinfe/semi-icons';
+import { getActiveElement } from '../_utils';
 
 let uuid = 0;
 
@@ -37,14 +38,17 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
     dialogId: string;
     private timeoutId: NodeJS.Timeout;
 
-
+    modalDialogRef: React.MutableRefObject<HTMLDivElement>;
+    foundation: ModalContentFoundation;
     constructor(props: ModalContentProps) {
         super(props);
         this.state = {
             dialogMouseDown: false,
+            prevFocusElement: getActiveElement(),
         };
         this.foundation = new ModalContentFoundation(this.adapter);
         this.dialogId = `dialog-${uuid++}`;
+        this.modalDialogRef = React.createRef();
     }
 
     get adapter(): ModalContentAdapter {
@@ -75,11 +79,30 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
                 }
             },
             getMouseState: () => this.state.dialogMouseDown,
+            modalDialogFocus: () => {
+                let activeElementInDialog;
+                if (this.modalDialogRef) {
+                    const activeElement = getActiveElement();
+                    activeElementInDialog = this.modalDialogRef.current.contains(activeElement);
+                }
+                if (!activeElementInDialog) {
+                    this.modalDialogRef && this.modalDialogRef.current.focus();
+                }
+            },
+            modalDialogBlur: () => {
+                this.modalDialogRef && this.modalDialogRef.current.blur();
+            },
+            prevFocusElementReFocus: () => {
+                const { prevFocusElement } = this.state;
+                const focus = get(prevFocusElement, 'focus');
+                isFunction(focus) && prevFocusElement.focus();
+            }
         };
     }
 
     componentDidMount() {
         this.foundation.handleKeyDownEventListenerMount();
+        this.foundation.modalDialogFocus();
     }
 
     componentWillUnmount() {
@@ -132,6 +155,7 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
             const iconType = closeIcon || <IconClose/>;
             closer = (
                 <Button
+                    aria-label="close"
                     className={`${cssClasses.DIALOG}-close`}
                     key="close-btn"
                     onClick={this.close}
@@ -162,7 +186,7 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
             (
                 <div className={`${cssClasses.DIALOG}-header`}>
                     {icon}
-                    <Typography.Title heading={5} className={`${cssClasses.DIALOG}-title`}>{title}</Typography.Title>
+                    <Typography.Title heading={5} className={`${cssClasses.DIALOG}-title`} id={`${cssClasses.DIALOG}-title`}>{title}</Typography.Title>
                     {closer}
                 </div>
             );
@@ -181,7 +205,7 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
         const icon = this.renderIcon();
         const hasHeader = title !== null && title !== undefined || 'header' in this.props;
         return hasHeader ?
-            <div className={bodyCls} style={bodyStyle}>{children}</div> :
+            <div className={bodyCls} id={`${cssClasses.DIALOG}-body`} style={bodyStyle}>{children}</div> :
             (
                 <div className={`${cssClasses.DIALOG}-body-wrapper`}>
                     {icon}
@@ -214,6 +238,7 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
         const header = this.renderHeader();
         const footer = props.footer ? <div className={`${cssClasses.DIALOG}-footer`}>{props.footer}</div> : null;
         const dialogElement = (
+            // eslint-disable-next-line jsx-a11y/no-static-element-interactions
             <div
                 key="dialog-element"
                 className={digCls}
@@ -222,6 +247,11 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
                 id={this.dialogId}
             >
                 <div
+                    role="dialog"
+                    ref={this.modalDialogRef}
+                    aria-modal="true"
+                    aria-labelledby={`${cssClasses.DIALOG}-title`}
+                    aria-describedby={`${cssClasses.DIALOG}-body`}
                     onAnimationEnd={props.onAnimationEnd}
                     className={cls([`${cssClasses.DIALOG}-content`,
                         props.contentClassName,
@@ -257,7 +287,7 @@ export default class ModalContent extends BaseComponent<ModalContentProps, Modal
             <div className={classList}>
                 {this.getMaskElement()}
                 <div
-                    role="modal"
+                    role="none"
                     tabIndex={-1}
                     className={`${cssClasses.DIALOG}-wrap`}
                     onClick={maskClosable ? this.onMaskClick : null}

+ 15 - 0
packages/semi-ui/navigation/Item.tsx

@@ -76,6 +76,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
         disabled: false,
     };
 
+    foundation: ItemFoundation;
     constructor(props: NavItemProps) {
         super(props);
         this.state = {
@@ -108,6 +109,8 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
             getIsCollapsed: () => this.props.isCollapsed || Boolean(this.context && this.context.isCollapsed) || false,
             getSelected: () =>
                 Boolean(this.context && this.context.selectedKeys && this.context.selectedKeys.includes(this.props.itemKey)),
+            getIsOpen: () =>
+                Boolean(this.context && this.context.openKeys && this.context.openKeys.includes(this.props.itemKey)),
         };
     }
 
@@ -159,6 +162,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
     };
 
     handleClick = (e: React.MouseEvent) => this.foundation.handleClick(e);
+    handleKeyPress = (e: React.KeyboardEvent) => this.foundation.handleKeyPress(e);
 
     render() {
         const {
@@ -249,15 +253,26 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
                 [`${clsPrefix}-collapsed`]: isCollapsed,
                 [`${clsPrefix}-disabled`]: disabled,
             });
+            const ariaProps = {
+                'aria-disabled': disabled,
+            };
+            if (isSubNav) {
+                const isOpen = this.adapter.getIsOpen();
+                ariaProps['aria-expanded'] = isOpen;
+            }
 
             itemDom = (
                 <li
+                    role="menuitem"
+                    tabIndex={-1}
+                    {...ariaProps}
                     style={style}
                     ref={this.setItemRef}
                     className={popoverItemCls}
                     onClick={this.handleClick}
                     onMouseEnter={onMouseEnter}
                     onMouseLeave={onMouseLeave}
+                    onKeyPress={this.handleKeyPress}
                 >
                     {itemChildren}
                 </li>

+ 13 - 1
packages/semi-ui/navigation/SubNav.tsx

@@ -115,6 +115,7 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
 
     titleRef: React.RefObject<HTMLDivElement>;
     itemRef: React.RefObject<HTMLLIElement>;
+    foundation: SubNavFoundation;
     constructor(props: SubNavProps) {
         super(props);
         this.state = {
@@ -171,6 +172,10 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
         this.foundation.handleClick(e && e.nativeEvent, this.titleRef && this.titleRef.current);
     };
 
+    handleKeyPress = (e: React.KeyboardEvent) => {
+        this.foundation.handleKeyPress(e && e.nativeEvent, this.titleRef && this.titleRef.current);
+    }
+
     handleDropdownVisible = (visible: boolean) => this.foundation.handleDropdownVisibleChange(visible);
 
     renderIcon(icon: React.ReactNode, pos: string, withTransition?: boolean, isToggleIcon = false, key: number | string = 0) {
@@ -239,7 +244,14 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
         }
 
         const titleDiv = (
-            <div ref={this.setTitleRef as any} className={titleCls} onClick={this.handleClick}>
+            <div
+                role="menuitem"
+                tabIndex={-1}
+                ref={this.setTitleRef as any} 
+                className={titleCls} 
+                onClick={this.handleClick}
+                onKeyPress={this.handleKeyPress}
+            >
                 <div className={`${prefixCls}-item-inner`}>
                     {placeholderIcons}
                     {this.context.toggleIconPosition === strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIconType, strings.ICON_POS_RIGHT, withTransition, true, 'key-toggle-position-left')}

+ 1 - 1
packages/semi-ui/navigation/index.tsx

@@ -408,7 +408,7 @@ class Nav extends BaseComponent<NavProps, NavState> {
                                 <div className={headerListOuterCls}>
                                     {headers}
                                     <div style={bodyStyle} className={`${prefixCls}-list-wrapper`}>
-                                        <ul className={`${prefixCls}-list`}>
+                                        <ul role="menu" aria-orientation={mode} className={`${prefixCls}-list`}>
                                             {this.adapter.getCache('itemElems')}
                                             {children}
                                         </ul>

+ 1 - 0
packages/semi-ui/radio/radio.tsx

@@ -82,6 +82,7 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
 
     radioEntity: RadioInner;
     context!: RadioContextValue;
+    foundation: RadioFoundation;
 
     constructor(props: RadioProps) {
         super(props);

+ 1 - 0
packages/semi-ui/radio/radioGroup.tsx

@@ -69,6 +69,7 @@ class RadioGroup extends BaseComponent<RadioGroupProps, RadioGroupState> {
         buttonSize: 'middle'
     };
 
+    foundation: RadioGroupFoundation;
     constructor(props: RadioGroupProps) {
         super(props);
         this.state = {

+ 2 - 0
packages/semi-ui/radio/radioInner.tsx

@@ -44,6 +44,7 @@ class RadioInner extends BaseComponent<RadioInnerProps, RadioInnerState> {
 
 
     inputEntity!: HTMLInputElement;
+    foundation: RadioInnerFoundation;
     constructor(props: RadioInnerProps) {
         super(props);
         this.state = {
@@ -114,6 +115,7 @@ class RadioInner extends BaseComponent<RadioInnerProps, RadioInnerState> {
                     ref={ref => {
                         this.inputEntity = ref;
                     }}
+                    // eslint-disable-next-line jsx-a11y/no-autofocus
                     autoFocus={autoFocus}
                     type={mode === 'advanced' ? 'checkbox' : 'radio'}
                     checked={Boolean(checked)}

+ 29 - 2
packages/semi-ui/select/index.tsx

@@ -325,6 +325,8 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.onMouseEnter = this.onMouseEnter.bind(this);
         this.onMouseLeave = this.onMouseLeave.bind(this);
         this.renderOption = this.renderOption.bind(this);
+        this.onKeyPress = this.onKeyPress.bind(this);
+        this.onClearBtnEnterPress = this.onClearBtnEnterPress.bind(this);
 
         this.foundation = new SelectFoundation(this.adapter);
 
@@ -626,6 +628,10 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.foundation.handleClearClick(e as any);
     }
 
+    onClearBtnEnterPress(e: React.KeyboardEvent) {
+        this.foundation.handleClearBtnEnterPress(e as any);
+    }
+
     renderEmpty() {
         return <Option empty={true} emptyContent={this.props.emptyContent} />;
     }
@@ -705,7 +711,8 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         const customCreateItem = renderCreateItem(option.value, isFocused);
 
         return (
-            <div onClick={e => this.onSelect(option, optionIndex, e)} key={new Date().valueOf()}>
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/interactive-supports-focus
+            <div role="button" aria-label="Use the input box to create an optional item" onClick={e => this.onSelect(option, optionIndex, e)} key={new Date().valueOf()}>
                 {customCreateItem}
             </div>
         );
@@ -771,6 +778,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             innerBottomSlot,
             loading,
             virtualize,
+            multiple,
         } = this.props;
 
         // Do a filter first, instead of directly judging in forEach, so that the focusIndex can correspond to
@@ -796,6 +804,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                     style={{ maxHeight: `${maxHeight}px` }}
                     className={optionListCls}
                     role="listbox"
+                    aria-multiselectable={multiple}
                     onScroll={e => this.foundation.handleListScroll(e)}
                 >
                     {innerTopSlot}
@@ -923,6 +932,10 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.foundation.handleMouseLeave(e as any);
     }
 
+    onKeyPress(e: React.KeyboardEvent) {
+        this.foundation.handleKeyPress(e as any);
+    }
+
     /* Processing logic when popover visible changes */
     handlePopoverVisibleChange(status) {
         const { virtualize } = this.props;
@@ -1047,7 +1060,14 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                 </Fragment>,
                 <Fragment key="clearicon">
                     {showClear ? (
-                        <div className={cls(`${prefixcls}-clear`)} onClick={this.onClear}>
+                        <div
+                            role="button"
+                            aria-label="Clear selected value"
+                            tabIndex={0}
+                            className={cls(`${prefixcls}-clear`)}
+                            onClick={this.onClear}
+                            onKeyPress={this.onClearBtnEnterPress}
+                        >
                             <IconClear />
                         </div>
                     ) : arrowContent}
@@ -1059,6 +1079,12 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         const tabIndex = disabled ? null : 0;
         return (
             <div
+                role="combobox"
+                aria-disabled={disabled}
+                aria-expanded={isOpen}
+                aria-controls={`${prefixcls}-${this.selectOptionListID}`}
+                aria-haspopup="listbox"
+                aria-label="select value"
                 className={selectionCls}
                 ref={ref => ((this.triggerRef as any).current = ref)}
                 onClick={e => this.foundation.handleClick(e)}
@@ -1068,6 +1094,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                 onMouseLeave={this.onMouseLeave}
                 // onFocus={e => this.foundation.handleTriggerFocus(e)}
                 onBlur={e => this.foundation.handleTriggerBlur(e as any)}
+                onKeyPress={this.onKeyPress}
                 {...keyboardEventSet}
             >
                 {inner}

+ 3 - 0
packages/semi-ui/select/option.tsx

@@ -134,6 +134,7 @@ class Option extends PureComponent<OptionProps> {
             }
         };
         return (
+            // eslint-disable-next-line jsx-a11y/interactive-supports-focus,jsx-a11y/click-events-have-key-events
             <div
                 className={optionClassName}
                 onClick={e => {
@@ -141,6 +142,8 @@ class Option extends PureComponent<OptionProps> {
                 }}
                 onMouseEnter={e => onMouseEnter && onMouseEnter(e)}
                 role="option"
+                aria-selected={selected ? "true" : "false"}
+                aria-disabled={disabled ? "true" : "false"}
                 style={style}
             >
                 {showTick ? (

+ 1 - 0
packages/semi-ui/spin/icon.tsx

@@ -29,6 +29,7 @@ function Icon(props: IconProps = {}) {
             viewBox="0 0 36 36"
             version="1.1"
             xmlns="http://www.w3.org/2000/svg"
+            aria-label="spin"
         >
             <defs>
                 <linearGradient x1="0%" y1="100%" x2="100%" y2="100%" id={id}>

+ 25 - 1
packages/semi-ui/table/Body/BaseRow.tsx

@@ -238,7 +238,7 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
             }
 
             if (isExpandedColumn(column) && !displayExpandedColumn) {
-                cells.push(<TableCell key={columnIndex} isSection={isSection} />);
+                cells.push(<TableCell key={columnIndex} colIndex={columnIndex} isSection={isSection} />);
             } else if (!isScrollbarColumn(column)) {
                 const diyProps: { width?: number } = {};
 
@@ -248,6 +248,7 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
 
                 cells.push(
                     <TableCell
+                        colIndex={columnIndex}
                         {...expandableProps}
                         {...diyProps}
                         hideExpandedColumn={hideExpandedColumn}
@@ -318,6 +319,10 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
             record,
             hovered,
             expanded,
+            expandableRow,
+            level,
+            expandedRow,
+            isSection
         } = this.props;
 
         const BodyRow: any = components.body.row;
@@ -341,9 +346,28 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
                     },
                     customClassName
                 );
+        const ariaProps = {};
+        if (typeof index === 'number') {
+            ariaProps['aria-rowindex'] = index + 1;
+        }
+        if (expandableRow) {
+            ariaProps['aria-expanded'] = expanded;
+        }
+        // if row is expandedRow, set it's level to 2 
+        if (expanded || expandedRow) {
+            ariaProps['aria-level'] = 2;
+        }
+        if (typeof level === 'number') {
+            ariaProps['aria-level'] = level + 1;
+        }
+        if (isSection) {
+            ariaProps['aria-level'] = 1;
+        }
 
         return (
             <BodyRow
+                role="row"
+                {...ariaProps}
                 {...rowProps}
                 style={baseRowStyle}
                 className={rowCls}

+ 7 - 2
packages/semi-ui/table/Body/index.tsx

@@ -3,7 +3,7 @@
 /* eslint-disable max-len */
 import React, { ReactNode } from 'react';
 import PropTypes from 'prop-types';
-import { get, size, isMap, each, isEqual, pick, isNull } from 'lodash';
+import { get, size, isMap, each, isEqual, pick, isNull, isFunction } from 'lodash';
 import classnames from 'classnames';
 import { VariableSizeList as List } from 'react-window';
 
@@ -15,7 +15,8 @@ import {
     isDisabled,
     getRecord,
     genExpandedRowKey,
-    getDefaultVirtualizedRowConfig
+    getDefaultVirtualizedRowConfig,
+    isTreeTable
 } from '@douyinfe/semi-foundation/table/utils';
 import BodyFoundation, { BodyAdapter, FlattenData, GroupFlattenData } from '@douyinfe/semi-foundation/table/bodyFoundation';
 import { strings } from '@douyinfe/semi-foundation/table/constants';
@@ -730,6 +731,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
             dataSource,
             onScroll,
             groups,
+            expandedRowRender,
         } = this.props;
 
         const x = get(scroll, 'x');
@@ -775,6 +777,9 @@ class Body extends BaseComponent<BodyProps, BodyState> {
                 onScroll={handleBodyScroll}
             >
                 <Table
+                    role={ isMap(groups) || isFunction(expandedRowRender) || isTreeTable({ dataSource }) ? 'treegrid' : 'grid'}
+                    aria-rowcount={dataSource && dataSource.length}
+                    aria-colcount={columns && columns.length}
                     style={tableStyle}
                     className={classnames(prefixCls, {
                         [`${prefixCls}-fixed`]: anyColumnFixed,

+ 7 - 1
packages/semi-ui/table/ColumnFilter.tsx

@@ -164,7 +164,13 @@ export default function ColumnFilter(props: ColumnFilterProps = {}): React.React
     } else {
         iconElem = (
             <div className={finalCls}>
-                <IconFilter size="small" />
+                <IconFilter
+                    role="button"
+                    aria-label="Filter data with this column"
+                    aria-haspopup="listbox"
+                    tabIndex={-1}
+                    size="small"
+                />
             </div>
         );
     }

+ 4 - 2
packages/semi-ui/table/ColumnSelection.tsx

@@ -20,6 +20,7 @@ export interface TableSelectionCellProps {
     indeterminate?: boolean; // Intermediate state, shown as a solid horizontal line
     prefixCls?: string;
     className?: string;
+    ariaLabel?: string;
 }
 
 /**
@@ -36,6 +37,7 @@ export default class TableSelectionCell extends BaseComponent<TableSelectionCell
         indeterminate: PropTypes.bool,
         prefixCls: PropTypes.string,
         className: PropTypes.string,
+        ariaLabel: PropTypes.string,
     };
 
     static defaultProps = {
@@ -59,7 +61,7 @@ export default class TableSelectionCell extends BaseComponent<TableSelectionCell
     handleChange = (e: CheckboxEvent) => this.foundation.handleChange(e);
 
     render() {
-        const { selected, getCheckboxProps, indeterminate, disabled, prefixCls, className } = this.props;
+        const { selected, getCheckboxProps, indeterminate, disabled, prefixCls, className, ariaLabel } = this.props;
         let checkboxProps = {
             onChange: this.handleChange,
             disabled,
@@ -81,7 +83,7 @@ export default class TableSelectionCell extends BaseComponent<TableSelectionCell
 
         return (
             <span className={wrapCls}>
-                <Checkbox {...checkboxProps} />
+                <Checkbox ariaLabel={ariaLabel} {...checkboxProps} />
             </span>
         );
     }

+ 18 - 1
packages/semi-ui/table/ColumnSorter.tsx

@@ -7,6 +7,7 @@ import { IconCaretup, IconCaretdown } from '@douyinfe/semi-icons';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/table/constants';
 
 import { SortOrder } from './interface';
+import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
 
 export interface ColumnSorterProps {
     className?: string;
@@ -43,9 +44,25 @@ export default class ColumnSorter extends PureComponent<ColumnSorterProps> {
         const downCls = cls(`${prefixCls}-column-sorter-down`, {
             on: sortOrder === strings.SORT_DIRECTIONS[1],
         });
+        const ariaProps = {
+            /**
+             * Set 'aria-sort' to aria-columnheader is difficult, so set 'aria-label' about sort info to sorter
+             * reference: https://developer.mozilla.org/en-US/docs/Web/API/Element/ariaSort
+             */
+            'aria-label': `Current sort order is ${sortOrder ? `${sortOrder}ing` : 'none'}`,
+            'aria-roledescription': 'Sort data with this column',
+        };
 
         return (
-            <div style={style} className={`${prefixCls}-column-sorter`} onClick={onClick}>
+            <div
+                role='button'
+                {...ariaProps}
+                tabIndex={-1}
+                style={style}
+                className={`${prefixCls}-column-sorter`}
+                onClick={onClick}
+                onKeyPress={e => isEnterPress(e) && onClick(e as any)}
+            >
                 <span className={`${upCls}`}>
                     <IconCaretup size={iconBtnSize} />
                 </span>

+ 5 - 0
packages/semi-ui/table/CustomExpandIcon.tsx

@@ -5,6 +5,7 @@ import { noop } from 'lodash';
 
 import { IconChevronRight, IconChevronDown, IconTreeTriangleDown, IconTreeTriangleRight } from '@douyinfe/semi-icons';
 import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
+import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
 
 import Rotate from '../motions/Rotate';
 
@@ -65,10 +66,14 @@ export default function CustomExpandIcon(props: CustomExpandIconProps) {
 
     return (
         <span
+            role="button"
+            aria-label="Expand this row"
+            tabIndex={-1}
             onClick={handleClick}
             onMouseEnter={onMouseEnter}
             onMouseLeave={onMouseLeave}
             className={`${prefixCls}-expand-icon`}
+            onKeyPress={e => isEnterPress(e) && handleClick(e as any)}
         >
             {icon}
         </span>

+ 2 - 0
packages/semi-ui/table/Table.tsx

@@ -790,6 +790,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                 const hasRowSelected = this.foundation.hasRowSelected(selectedRowKeys, allRowKeysSet);
                 return (
                     <ColumnSelection
+                        ariaLabel={`${allIsSelected ? 'Deselect' : 'Select'} all rows`}
                         disabled={disabled}
                         key={columnKey}
                         selected={allIsSelected}
@@ -806,6 +807,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
 
                 return (
                     <ColumnSelection
+                        ariaLabel={`${selected ? 'Select' : 'Deselect'} this row`}
                         getCheckboxProps={checkboxPropsFn}
                         selected={selected}
                         onChange={(status, e) => this.toggleSelectRow(status, key, e)}

+ 11 - 1
packages/semi-ui/table/TableCell.tsx

@@ -42,6 +42,7 @@ export interface TableCellProps extends BaseProps {
     selected?: boolean; // Whether the current row is selected
     expanded?: boolean; // Whether the current line is expanded
     disabled?: boolean;
+    colIndex?: number;
 }
 
 function isInvalidRenderCellText(text: any) {
@@ -82,6 +83,7 @@ export default class TableCell extends BaseComponent<TableCellProps, Record<stri
         height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
         selected: PropTypes.bool,
         expanded: PropTypes.bool,
+        colIndex: PropTypes.number,
     };
 
     get adapter(): TableCellAdapter {
@@ -314,6 +316,7 @@ export default class TableCell extends BaseComponent<TableCellProps, Record<stri
             fixedRight,
             lastFixedLeft,
             firstFixedRight,
+            colIndex
         } = this.props;
         const { className } = column;
         const fixedLeftFlag = fixedLeft || typeof fixedLeft === 'number';
@@ -347,7 +350,14 @@ export default class TableCell extends BaseComponent<TableCellProps, Record<stri
         );
 
         return (
-            <BodyCell className={columnCls} onClick={this.handleClick} {...newTdProps} ref={this.setRef}>
+            <BodyCell
+                role="gridcell"
+                aria-colindex={colIndex + 1}
+                className={columnCls}
+                onClick={this.handleClick}
+                {...newTdProps}
+                ref={this.setRef}
+            >
                 {inner}
             </BodyCell>
         );

+ 16 - 2
packages/semi-ui/table/TableHeaderRow.tsx

@@ -168,11 +168,25 @@ export default class TableHeaderRow extends BaseComponent<TableHeaderRowProps, R
                 return null;
             }
 
-            return <HeaderCell {...props} style={cellStyle} key={column.key || column.dataIndex || cellIndex} />;
+            return (
+                <HeaderCell
+                    role="columnheader"
+                    aria-colindex={cellIndex + 1}
+                    {...props}
+                    style={cellStyle} 
+                    key={column.key || column.dataIndex || cellIndex} 
+                />
+            );
         });
 
         return (
-            <HeaderRow {...rowProps} style={style} ref={this.cacheRef}>
+            <HeaderRow
+                role="row"
+                aria-rowindex={index + 1}
+                {...rowProps}
+                style={style}
+                ref={this.cacheRef}
+            >
                 {cells}
             </HeaderRow>
         );

+ 2 - 0
packages/semi-ui/toast/toast.tsx

@@ -118,6 +118,8 @@ class Toast extends BaseComponent<ToastReactProps, ToastState> {
         const btnSize = 'small';
         return (
             <div
+                role='alert'
+                aria-label={`${type ? type : 'default'} type`}
                 className={toastCls}
                 style={style}
                 onMouseEnter={this.clearCloseTimer}