Repository: ant-design/ant-design-mobile
Pull request #6587 (2024-04-03 14:00:44)

feat(ConfigProvider): add icons config
增加一种通过 `<ConfigProvider>` 设置全局图标替换的方式：

```jsx
import { SuccessFill, ErrorFill, Loading } from 'my-icon-pack'

<ConfigProvider icons={{
  ToastSuccess: SuccessFill,
  ToastFail: ErrorFill,
  ToastLoading: Loading,
}}>
  <App/>
</ConfigProvider>
```

github-actions[bot]: PR preview has been successfully built and deployed to https://antd-mobile-preview-pr-6587.surge.sh
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) Report
Attention: Patch coverage is `85.05747%` with `13 lines` in your changes are missing coverage. Please review.
> Project coverage is 92.23%. Comparing base [(`cf550e4`)](https://app.codecov.io/gh/ant-design/ant-design-mobile/commit/cf550e43989cbe9f0cc2c78cb84dd57ac5b4fff9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) to head [(`76104a8`)](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).

| [Files](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) | Patch % | Lines |
|---|---|---|
| [src/components/result/result-icon.tsx](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?src=pr&el=tree&filepath=src%2Fcomponents%2Fresult%2Fresult-icon.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvcmVzdWx0L3Jlc3VsdC1pY29uLnRzeA==) | 56.52% | [10 Missing :warning: ](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) |
| [src/components/nav-bar/nav-bar.tsx](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?src=pr&el=tree&filepath=src%2Fcomponents%2Fnav-bar%2Fnav-bar.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvbmF2LWJhci9uYXYtYmFyLnRzeA==) | 25.00% | [3 Missing :warning: ](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #6587      +/-   ##
==========================================
- Coverage   92.34%   92.23%   -0.11%     
==========================================
  Files         318      319       +1     
  Lines        6910     6969      +59     
  Branches     1729     1760      +31     
==========================================
+ Hits         6381     6428      +47     
- Misses        493      505      +12     
  Partials       36       36              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6587?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).

github-actions[bot]: ## size-limit report 📦 
| Path                             | Size                  | Loading time (3g) | Running time (snapdragon) | Total time |
| -------------------------------- | --------------------- | ----------------- | ------------------------- | ---------- |
| `./lib/bundle/antd-mobile.es.js` | 137.84 KB (+494 B 🔺) | 2.8 s (+0.36% 🔺) | 325 ms (-6.18% 🔽)        | 3.1 s      |
github-actions[bot]: **Size Change:** +1.01 kB (0%) 

**Total Size:** 354 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./lib/bundle/antd-mobile.compatible.umd.js` | 190 kB | +594 B (0%) |
| `./lib/bundle/antd-mobile.umd.js` | 145 kB | +419 B (0%) |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `./lib/bundle/css-vars-patch.css` | 1.43 kB |
| `./lib/bundle/style.css` | 18 kB |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>

Patch:
diff --git a/src/components/center-popup/center-popup.tsx b/src/components/center-popup/center-popup.tsx
index 193600c02..29f26dd9f 100644
--- a/src/components/center-popup/center-popup.tsx
+++ b/src/components/center-popup/center-popup.tsx
@@ -1,20 +1,20 @@
-import React, { useRef, useState } from 'react'
-import type { FC, PropsWithChildren } from 'react'
-import { renderToContainer } from '../../utils/render-to-container'
-import Mask from '../mask'
-import { withStopPropagation } from '../../utils/with-stop-propagation'
-import { mergeProps } from '../../utils/with-default-props'
-import { useIsomorphicLayoutEffect, useUnmountedRef } from 'ahooks'
 import { animated, useSpring } from '@react-spring/web'
-import { useInnerVisible } from '../../utils/use-inner-visible'
+import { useIsomorphicLayoutEffect, useUnmountedRef } from 'ahooks'
 import classNames from 'classnames'
+import type { FC, PropsWithChildren } from 'react'
+import React, { useRef, useState } from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
+import { renderToContainer } from '../../utils/render-to-container'
 import { ShouldRender } from '../../utils/should-render'
+import { useInnerVisible } from '../../utils/use-inner-visible'
 import { useLockScroll } from '../../utils/use-lock-scroll'
-import { CloseOutline } from 'antd-mobile-icons'
+import { mergeProps } from '../../utils/with-default-props'
+import { withStopPropagation } from '../../utils/with-stop-propagation'
+import { useConfig } from '../config-provider'
+import Mask from '../mask'
 import {
-  defaultPopupBaseProps,
   PopupBaseProps,
+  defaultPopupBaseProps,
 } from '../popup/popup-base-props'
 
 const classPrefix = 'adm-center-popup'
@@ -37,13 +37,14 @@ const defaultProps = {
   getContainer: null,
 }
 
-export const CenterPopup: FC<CenterPopupProps> = p => {
-  const props = mergeProps(defaultProps, p)
+export const CenterPopup: FC<CenterPopupProps> = props => {
+  const { popup: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(defaultProps, componentConfig, props)
 
   const unmountedRef = useUnmountedRef()
   const style = useSpring({
-    scale: props.visible ? 1 : 0.8,
-    opacity: props.visible ? 1 : 0,
+    scale: mergedProps.visible ? 1 : 0.8,
+    opacity: mergedProps.visible ? 1 : 0,
     config: {
       mass: 1.2,
       tension: 200,
@@ -52,40 +53,40 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
     },
     onRest: () => {
       if (unmountedRef.current) return
-      setActive(props.visible)
-      if (props.visible) {
-        props.afterShow?.()
+      setActive(mergedProps.visible)
+      if (mergedProps.visible) {
+        mergedProps.afterShow?.()
       } else {
-        props.afterClose?.()
+        mergedProps.afterClose?.()
       }
     },
   })
 
-  const [active, setActive] = useState(props.visible)
+  const [active, setActive] = useState(mergedProps.visible)
   useIsomorphicLayoutEffect(() => {
-    if (props.visible) {
+    if (mergedProps.visible) {
       setActive(true)
     }
-  }, [props.visible])
+  }, [mergedProps.visible])
 
   const ref = useRef<HTMLDivElement>(null)
-  useLockScroll(ref, props.disableBodyScroll && active)
+  useLockScroll(ref, mergedProps.disableBodyScroll && active)
 
-  const maskVisible = useInnerVisible(active && props.visible)
+  const maskVisible = useInnerVisible(active && mergedProps.visible)
 
   const body = (
     <div
-      className={classNames(`${classPrefix}-body`, props.bodyClassName)}
-      style={props.bodyStyle}
+      className={classNames(`${classPrefix}-body`, mergedProps.bodyClassName)}
+      style={mergedProps.bodyStyle}
     >
-      {props.children}
+      {mergedProps.children}
     </div>
   )
 
   const node = withStopPropagation(
-    props.stopPropagation,
+    mergedProps.stopPropagation,
     withNativeProps(
-      props,
+      mergedProps,
       <div
         className={classPrefix}
         style={{
@@ -93,27 +94,30 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
           pointerEvents: active ? undefined : 'none',
         }}
       >
-        {props.mask && (
+        {mergedProps.mask && (
           <Mask
             visible={maskVisible}
-            forceRender={props.forceRender}
-            destroyOnClose={props.destroyOnClose}
+            forceRender={mergedProps.forceRender}
+            destroyOnClose={mergedProps.destroyOnClose}
             onMaskClick={e => {
-              props.onMaskClick?.(e)
-              if (props.closeOnMaskClick) {
-                props.onClose?.()
+              mergedProps.onMaskClick?.(e)
+              if (mergedProps.closeOnMaskClick) {
+                mergedProps.onClose?.()
               }
             }}
-            style={props.maskStyle}
-            className={classNames(`${classPrefix}-mask`, props.maskClassName)}
+            style={mergedProps.maskStyle}
+            className={classNames(
+              `${classPrefix}-mask`,
+              mergedProps.maskClassName
+            )}
             disableBodyScroll={false}
-            stopPropagation={props.stopPropagation}
+            stopPropagation={mergedProps.stopPropagation}
           />
         )}
         <div
           className={`${classPrefix}-wrap`}
-          role={props.role}
-          aria-label={props['aria-label']}
+          role={mergedProps.role}
+          aria-label={mergedProps['aria-label']}
         >
           <animated.div
             style={{
@@ -124,17 +128,17 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
             }}
             ref={ref}
           >
-            {props.showCloseButton && (
+            {mergedProps.showCloseButton && (
               <a
                 className={classNames(
                   `${classPrefix}-close`,
                   'adm-plain-anchor'
                 )}
                 onClick={() => {
-                  props.onClose?.()
+                  mergedProps.onClose?.()
                 }}
               >
-                <CloseOutline />
+                {mergedProps.closeIcon}
               </a>
             )}
             {body}
@@ -147,10 +151,10 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
   return (
     <ShouldRender
       active={active}
-      forceRender={props.forceRender}
-      destroyOnClose={props.destroyOnClose}
+      forceRender={mergedProps.forceRender}
+      destroyOnClose={mergedProps.destroyOnClose}
     >
-      {renderToContainer(props.getContainer, node)}
+      {renderToContainer(mergedProps.getContainer, node)}
     </ShouldRender>
   )
 }
diff --git a/src/components/check-list/check-list.tsx b/src/components/check-list/check-list.tsx
index c83efe136..74b6ec01d 100644
--- a/src/components/check-list/check-list.tsx
+++ b/src/components/check-list/check-list.tsx
@@ -1,11 +1,12 @@
-import React from 'react'
+import { CheckOutline } from 'antd-mobile-icons'
 import type { FC, ReactNode } from 'react'
+import React from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
-import List, { ListProps } from '../list'
+import { usePropsValue } from '../../utils/use-props-value'
 import { mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
+import List, { ListProps } from '../list'
 import { CheckListContext } from './context'
-import { usePropsValue } from '../../utils/use-props-value'
-import { CheckOutline } from 'antd-mobile-icons'
 
 const classPrefix = 'adm-check-list'
 
@@ -29,13 +30,14 @@ const defaultProps = {
   activeIcon: <CheckOutline />,
 }
 
-export const CheckList: FC<CheckListProps> = p => {
-  const props = mergeProps(defaultProps, p)
+export const CheckList: FC<CheckListProps> = props => {
+  const { checkList: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(defaultProps, componentConfig, props)
 
-  const [value, setValue] = usePropsValue(props)
+  const [value, setValue] = usePropsValue(mergedProps)
 
   function check(val: CheckListValue) {
-    if (props.multiple) {
+    if (mergedProps.multiple) {
       setValue([...value, val])
     } else {
       setValue([val])
@@ -46,7 +48,7 @@ export const CheckList: FC<CheckListProps> = p => {
     setValue(value.filter(item => item !== val))
   }
 
-  const { activeIcon, extra, disabled, readOnly } = props
+  const { activeIcon, extra, disabled, readOnly } = mergedProps
 
   return (
     <CheckListContext.Provider
@@ -61,9 +63,9 @@ export const CheckList: FC<CheckListProps> = p => {
       }}
     >
       {withNativeProps(
-        props,
-        <List mode={props.mode} className={classPrefix}>
-          {props.children}
+        mergedProps,
+        <List mode={mergedProps.mode} className={classPrefix}>
+          {mergedProps.children}
         </List>
       )}
     </CheckListContext.Provider>
diff --git a/src/components/collapse/collapse.tsx b/src/components/collapse/collapse.tsx
index 816c5cf66..2d544ca68 100644
--- a/src/components/collapse/collapse.tsx
+++ b/src/components/collapse/collapse.tsx
@@ -10,6 +10,8 @@ import { traverseReactNode } from '../../utils/traverse-react-node'
 import { useIsomorphicUpdateLayoutEffect } from '../../utils/use-isomorphic-update-layout-effect'
 import { observe } from '../../utils/use-mutation-effect'
 import { usePropsValue } from '../../utils/use-props-value'
+import { mergeProp, mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
 import List from '../list'
 
 const classPrefix = `adm-collapse`
@@ -21,8 +23,12 @@ export type CollapsePanelProps = {
   forceRender?: boolean
   destroyOnClose?: boolean
   onClick?: (event: React.MouseEvent<Element, MouseEvent>) => void
-  arrow?: ReactNode | ((active: boolean) => ReactNode)
+  arrowIcon?: ReactNode | ((active: boolean) => ReactNode)
   children?: ReactNode
+  /**
+   * @deprecated use `arrowIcon` instead
+   */
+  arrow?: ReactNode | ((active: boolean) => ReactNode)
 } & NativeProps
 
 export const CollapsePanel: FC<CollapsePanelProps> = () => {
@@ -121,6 +127,10 @@ type ValueProps<T> = {
   activeKey?: T
   defaultActiveKey?: T
   onChange?: (activeKey: T) => void
+  arrowIcon?: ReactNode | ((active: boolean) => ReactNode)
+  /**
+   * @deprecated use `arrowIcon` instead
+   */
   arrow?: ReactNode | ((active: boolean) => ReactNode)
 }
 
@@ -136,8 +146,10 @@ export type CollapseProps = (
 } & NativeProps
 
 export const Collapse: FC<CollapseProps> = props => {
+  const { collapse: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(componentConfig, props)
   const panels: ReactElement<CollapsePanelProps>[] = []
-  traverseReactNode(props.children, child => {
+  traverseReactNode(mergedProps.children, child => {
     if (!isValidElement<CollapsePanelProps>(child)) return
     const key = child.key
     if (typeof key !== 'string') return
@@ -146,11 +158,11 @@ export const Collapse: FC<CollapseProps> = props => {
   })
 
   const handlePropsValue = () => {
-    if (!props.accordion) {
+    if (!mergedProps.accordion) {
       return {
-        value: props.activeKey,
-        defaultValue: props.defaultActiveKey ?? [],
-        onChange: props.onChange,
+        value: mergedProps.activeKey,
+        defaultValue: mergedProps.defaultActiveKey ?? [],
+        onChange: mergedProps.onChange,
       }
     }
 
@@ -162,20 +174,22 @@ export const Collapse: FC<CollapseProps> = props => {
       value: [],
       defaultValue: [],
       onChange: v => {
-        props.onChange?.(v[0] ?? null)
+        mergedProps.onChange?.(v[0] ?? null)
       },
     }
 
-    if (props.activeKey === undefined) {
+    if (mergedProps.activeKey === undefined) {
       initValue.value = undefined
-    } else if (props.activeKey !== null) {
-      initValue.value = [props.activeKey]
+    } else if (mergedProps.activeKey !== null) {
+      initValue.value = [mergedProps.activeKey]
     }
 
     if (
-      ![null, undefined].includes(props.defaultActiveKey as null | undefined)
+      ![null, undefined].includes(
+        mergedProps.defaultActiveKey as null | undefined
+      )
     ) {
-      initValue.defaultValue = [props.defaultActiveKey as string]
+      initValue.defaultValue = [mergedProps.defaultActiveKey as string]
     }
 
     return initValue
@@ -187,14 +201,14 @@ export const Collapse: FC<CollapseProps> = props => {
     activeKey === null ? [] : Array.isArray(activeKey) ? activeKey : [activeKey]
 
   return withNativeProps(
-    props,
+    mergedProps,
     <div className={classPrefix}>
       <List>
         {panels.map(panel => {
           const key = panel.key as string
           const active = activeKeyList.includes(key)
           function handleClick(event: React.MouseEvent<Element, MouseEvent>) {
-            if (props.accordion) {
+            if (mergedProps.accordion) {
               if (active) {
                 setActiveKey([])
               } else {
@@ -211,15 +225,16 @@ export const Collapse: FC<CollapseProps> = props => {
             panel.props.onClick?.(event)
           }
 
-          const renderArrow = () => {
-            let arrow: CollapseProps['arrow'] = <DownOutline />
-            if (props.arrow !== undefined) {
-              arrow = props.arrow
-            }
-            if (panel.props.arrow !== undefined) {
-              arrow = panel.props.arrow
-            }
-            return typeof arrow === 'function' ? (
+          const arrow = mergeProp(
+            <DownOutline />,
+            mergedProps.arrow,
+            mergedProps.arrowIcon,
+            panel.props.arrow,
+            panel.props.arrowIcon
+          )
+
+          const arrowIcon =
+            typeof arrow === 'function' ? (
               arrow(active)
             ) : (
               <div
@@ -230,7 +245,6 @@ export const Collapse: FC<CollapseProps> = props => {
                 {arrow}
               </div>
             )
-          }
 
           return (
             <React.Fragment key={key}>
@@ -240,7 +254,7 @@ export const Collapse: FC<CollapseProps> = props => {
                   className={`${classPrefix}-panel-header`}
                   onClick={handleClick}
                   disabled={panel.props.disabled}
-                  arrow={renderArrow()}
+                  arrowIcon={arrowIcon}
                 >
                   {panel.props.title}
                 </List.Item>
diff --git a/src/components/collapse/index.en.md b/src/components/collapse/index.en.md
index fb6dd655c..66e835d27 100644
--- a/src/components/collapse/index.en.md
+++ b/src/components/collapse/index.en.md
@@ -21,7 +21,7 @@ A content area that can be collapsed/expanded.
 | --- | --- | --- | --- |
 | accordion | Whether to enable accordion mode | `boolean` | `false` |
 | activeKey | The `key` of the currently expanded panel | accordion mode: `string \| null` <br/>non-accordion mode: `string[]` | - |
-| arrow | Custom arrow. if you pass a ReactNode, antd-mobile will add a rotate animation for you. | `React.ReactNode \| ((active: boolean) => React.ReactNode)` | - |
+| arrowIcon | Custom arrow icon. if you pass a ReactNode, antd-mobile will add a rotate animation for you. | `React.ReactNode \| ((active: boolean) => React.ReactNode)` | - |
 | defaultActiveKey | The `key` of the expanded panel by default | accordion mode: `string \| null` <br/>non-accordion mode: `string[]` | - |
 | onChange | Triggered when the panel is switched | accordion mode: `(activeKey: string \| null) => void` <br /> non-accordion mode: `(activeKey: string[]) => void` | - |
 
@@ -31,7 +31,7 @@ A content area that can be collapsed/expanded.
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| arrow | Custom arrow | `React.ReactNode \| ((active: boolean) => React.ReactNode)` | - |
+| arrowIcon | Custom arrow icon | `React.ReactNode \| ((active: boolean) => React.ReactNode)` | - |
 | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` |
 | disabled | Whether disabled or not | `boolean` | `false` |
 | forceRender | Whether to render the `DOM` structure when hidden | `boolean` | `false` |
diff --git a/src/components/collapse/index.zh.md b/src/components/collapse/index.zh.md
index 44b5ef877..dff5354cd 100644
--- a/src/components/collapse/index.zh.md
+++ b/src/components/collapse/index.zh.md
@@ -21,7 +21,7 @@
 | --- | --- | --- | --- |
 | accordion | 是否开启手风琴模式 | `boolean` | `false` |
 | activeKey | 当前展开面板的 `key` | 手风琴模式：`string \| null` <br/>非手风琴模式：`string[]` | - |
-| arrow | 自定义箭头，如果是 ReactNode，那么 antd-mobile 会自动为你增加旋转动画效果 | `ReactNode \| ((active: boolean) => React.ReactNode)` | - |
+| arrowIcon | 自定义箭头图标，如果是 ReactNode，那么 antd-mobile 会自动为你增加旋转动画效果 | `ReactNode \| ((active: boolean) => React.ReactNode)` | - |
 | defaultActiveKey | 默认展开面板的 `key` | 手风琴模式：`string \| null` <br/>非手风琴模式：`string[]` | - |
 | onChange | 切换面板时触发 | 手风琴模式：`(activeKey: string \| null) => void` <br /> 非手风琴模式：`(activeKey: string[]) => void` | - |
 
@@ -31,7 +31,7 @@
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| arrow | 自定义箭头 | `ReactNode \| ((active: boolean) => React.ReactNode)` | - |
+| arrowIcon | 自定义箭头图标 | `ReactNode \| ((active: boolean) => React.ReactNode)` | - |
 | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` |
 | disabled | 是否为禁用状态 | `boolean` | `false` |
 | forceRender | 被隐藏时是否渲染 `DOM` 结构 | `boolean` | `false` |
diff --git a/src/components/config-provider/config-provider.tsx b/src/components/config-provider/config-provider.tsx
index f608e45f5..6435035ad 100644
--- a/src/components/config-provider/config-provider.tsx
+++ b/src/components/config-provider/config-provider.tsx
@@ -1,10 +1,47 @@
-import React, { useContext } from 'react'
-import type { FC, ReactNode } from 'react'
+import React, { FC, ReactNode, useContext } from 'react'
 import { Locale } from '../../locales/base'
 import zhCN from '../../locales/zh-CN'
 
 type Config = {
   locale: Locale
+  checkList?: {
+    activeIcon?: ReactNode
+  }
+  collapse?: {
+    arrowIcon?: ReactNode | ((active: boolean) => ReactNode)
+  }
+  dropdown?: {
+    arrowIcon?: ReactNode
+  }
+  form?: {
+    helpIcon?: ReactNode
+  }
+  input?: {
+    clearIcon?: ReactNode
+  }
+  list?: {
+    arrowIcon?: ReactNode
+  }
+  navBar?: {
+    backIcon?: ReactNode
+  }
+  noticeBar?: {
+    icon?: ReactNode
+    closeIcon?: ReactNode
+  }
+  popup?: {
+    closeIcon?: ReactNode
+  }
+  result?: {
+    successIcon?: ReactNode
+    errorIcon?: ReactNode
+    infoIcon?: ReactNode
+    waitingIcon?: ReactNode
+    warningIcon?: ReactNode
+  }
+  searchBar?: {
+    searchIcon?: ReactNode
+  }
 }
 
 export const defaultConfigRef: {
@@ -25,7 +62,7 @@ export function getDefaultConfig() {
 
 const ConfigContext = React.createContext<Config | null>(null)
 
-export type ConfigProviderProps = Config & {
+export type ConfigProviderProps = Partial<Config> & {
   children?: ReactNode
 }
 
diff --git a/src/components/config-provider/index.en.md b/src/components/config-provider/index.en.md
index c03304a3f..e435e5cfe 100644
--- a/src/components/config-provider/index.en.md
+++ b/src/components/config-provider/index.en.md
@@ -1,7 +1,36 @@
 # ConfigProvider
 
+Configure locale messages and custom icons globally.
+
+## When to use
+
+- You want to use other languages than English
+- You want to use your own icon set instead of `antd-mobile-icons`
+
 ## Demos
 
 <code src="./demos/demo1.tsx" ></code>
 
 <code src="./demos/demo2.tsx" ></code>
+
+## ConfigProvider
+
+### Props
+
+| Name | Description | Type | Default |
+| --- | --- | --- | --- |
+| locale | Locale messages | `Locale` | [zh-CN] |
+| checkList | CheckList config | `{ activeIcon?: ReactNode }` | - |
+| collapse | Collapse config | `{ arrowIcon?: ReactNode \| ((active: boolean) => ReactNode) }` | - |
+| dropdown | Dropdown config | `{ arrowIcon?: ReactNode }` | - |
+| form | Form config | `{ helpIcon?: ReactNode }` | - |
+| input | Input config | `{ clearIcon?: ReactNode }` | - |
+| list | List config | `{ arrowIcon?: ReactNode }` | - |
+| navBar | NavBar config | `{ backIcon?: ReactNode }` | - |
+| noticeBar | NoticeBar config | `{ icon?: ReactNode, closeIcon?: ReactNode }` | - |
+| popup | Popup config | `{ closeIcon?: ReactNode }` | - |
+| result | Result config | `{ successIcon?: ReactNode, errorIcon?: ReactNode, infoIcon?: ReactNode, waitingIcon?: ReactNode, warningIcon?: ReactNode }` | - |
+| searchBar | SearchBar config | `{ icon?: ReactNode }` | - |
+| toast | Toast config | `{ successIcon?: ReactNode, failIcon?: ReactNode, loadingIcon?: ReactNode }` | - |
+
+[zh-CN]: https://github.com/ant-design/ant-design-mobile/blob/master/src/locales/zh-CN.ts
diff --git a/src/components/config-provider/index.zh.md b/src/components/config-provider/index.zh.md
index 84560f3ee..6011631fc 100644
--- a/src/components/config-provider/index.zh.md
+++ b/src/components/config-provider/index.zh.md
@@ -1,7 +1,36 @@
 # ConfigProvider 配置
 
+用于全局配置本地化文案和个性化图标。
+
+## 何时使用
+
+- 您想使用除英文之外的语言。
+- 您想使用自己的图标集，而不是内置的 `antd-mobile-icons`。
+
 ## 示例
 
 <code src="./demos/demo1.tsx" ></code>
 
 <code src="./demos/demo2.tsx" ></code>
+
+## ConfigProvider
+
+### 属性
+
+| 属性 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| locale | 本地化文案 | `Locale` | [zh-CN] |
+| checkList | CheckList 配置 | `{ activeIcon?: ReactNode }` | - |
+| collapse | Collapse 配置 | `{ arrowIcon?: ReactNode \| ((active: boolean) => ReactNode) }` | - |
+| dropdown | Dropdown 配置 | `{ arrowIcon?: ReactNode }` | - |
+| form | Form 配置 | `{ helpIcon?: ReactNode }` | - |
+| input | Input 配置 | `{ clearIcon?: ReactNode }` | - |
+| list | List 配置 | `{ arrowIcon?: ReactNode }` | - |
+| navBar | NavBar 配置 | `{ backIcon?: ReactNode }` | - |
+| noticeBar | NoticeBar 配置 | `{ icon?: ReactNode, closeIcon?: ReactNode }` | - |
+| popup | Popup 配置 | `{ closeIcon?: ReactNode }` | - |
+| result | Result 配置 | `{ successIcon?: ReactNode, errorIcon?: ReactNode, infoIcon?: ReactNode, waitingIcon?: ReactNode, warningIcon?: ReactNode }` | - |
+| searchBar | SearchBar 配置 | `{ icon?: ReactNode }` | - |
+| toast | Toast 配置 | `{ successIcon?: ReactNode, failIcon?: ReactNode, loadingIcon?: ReactNode }` | - |
+
+[zh-CN]: https://github.com/ant-design/ant-design-mobile/blob/master/src/locales/zh-CN.ts
diff --git a/src/components/dropdown/context.tsx b/src/components/dropdown/context.tsx
new file mode 100644
index 000000000..9110b0eb9
--- /dev/null
+++ b/src/components/dropdown/context.tsx
@@ -0,0 +1,3 @@
+import React from 'react'
+
+export const IconContext = React.createContext<React.ReactNode>(null)
diff --git a/src/components/dropdown/dropdown.tsx b/src/components/dropdown/dropdown.tsx
index 993ba2270..e1802be37 100644
--- a/src/components/dropdown/dropdown.tsx
+++ b/src/components/dropdown/dropdown.tsx
@@ -1,26 +1,28 @@
 import { useClickAway } from 'ahooks'
 import classNames from 'classnames'
+import type {
+  ComponentProps,
+  PropsWithChildren,
+  ReactElement,
+  ReactNode,
+} from 'react'
 import React, {
   cloneElement,
+  forwardRef,
+  isValidElement,
   useEffect,
+  useImperativeHandle,
   useRef,
   useState,
-  forwardRef,
-  useImperativeHandle,
-  isValidElement,
 } from 'react'
-import type {
-  ReactNode,
-  ComponentProps,
-  PropsWithChildren,
-  ReactElement,
-} from 'react'
-import Popup, { PopupProps } from '../popup'
-import Item, { ItemChildrenWrap } from './item'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
-import { mergeProps } from '../../utils/with-default-props'
 import { usePropsValue } from '../../utils/use-props-value'
+import { mergeProp, mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
+import Popup, { PopupProps } from '../popup'
 import { defaultPopupBaseProps } from '../popup/popup-base-props'
+import { IconContext } from './context'
+import Item, { ItemChildrenWrap } from './item'
 
 const classPrefix = `adm-dropdown`
 
@@ -30,6 +32,10 @@ export type DropdownProps = {
   closeOnMaskClick?: boolean
   closeOnClickAway?: boolean
   onChange?: (key: string | null) => void
+  arrowIcon?: ReactNode
+  /**
+   * @deprecated use `arrowIcon` instead
+   */
   arrow?: ReactNode
   getContainer?: PopupProps['getContainer']
 } & NativeProps
@@ -46,12 +52,18 @@ export type DropdownRef = {
 }
 
 const Dropdown = forwardRef<DropdownRef, PropsWithChildren<DropdownProps>>(
-  (p, ref) => {
-    const props = mergeProps(defaultProps, p)
+  (props, ref) => {
+    const { dropdown: componentConfig = {} } = useConfig()
+    const mergedProps = mergeProps(defaultProps, componentConfig, props)
+    const arrowIcon = mergeProp(
+      componentConfig.arrowIcon,
+      props.arrow,
+      props.arrowIcon
+    )
     const [value, setValue] = usePropsValue({
-      value: props.activeKey,
-      defaultValue: props.defaultActiveKey,
-      onChange: props.onChange,
+      value: mergedProps.activeKey,
+      defaultValue: mergedProps.defaultActiveKey,
+      onChange: mergedProps.onChange,
     })
 
     const navRef = useRef<HTMLDivElement>(null)
@@ -59,7 +71,7 @@ const Dropdown = forwardRef<DropdownRef, PropsWithChildren<DropdownProps>>(
 
     // 点击外部区域，关闭
     useClickAway(() => {
-      if (!props.closeOnClickAway) return
+      if (!mergedProps.closeOnClickAway) return
       setValue(null)
     }, [navRef, contentRef])
 
@@ -85,7 +97,7 @@ const Dropdown = forwardRef<DropdownRef, PropsWithChildren<DropdownProps>>(
 
     let popupForceRender = false
     const items: ReactElement<ComponentProps<typeof Item>>[] = []
-    const navs = React.Children.map(props.children, child => {
+    const navs = React.Children.map(mergedProps.children, child => {
       if (isValidElement<ComponentProps<typeof Item>>(child)) {
         const childProps = {
           ...child.props,
@@ -94,8 +106,6 @@ const Dropdown = forwardRef<DropdownRef, PropsWithChildren<DropdownProps>>(
             child.props.onClick?.(event)
           },
           active: child.key === value,
-          arrow:
-            child.props.arrow === undefined ? props.arrow : child.props.arrow,
         }
         items.push(child)
         if (child.props.forceRender) popupForceRender = true
@@ -116,27 +126,29 @@ const Dropdown = forwardRef<DropdownRef, PropsWithChildren<DropdownProps>>(
     )
 
     return withNativeProps(
-      props,
+      mergedProps,
       <div
         className={classNames(classPrefix, {
           [`${classPrefix}-open`]: !!value,
         })}
         ref={containerRef}
       >
-        <div className={`${classPrefix}-nav`} ref={navRef}>
-          {navs}
-        </div>
+        <IconContext.Provider value={arrowIcon}>
+          <div className={`${classPrefix}-nav`} ref={navRef}>
+            {navs}
+          </div>
+        </IconContext.Provider>
         <Popup
           visible={!!value}
           position='top'
-          getContainer={props.getContainer}
+          getContainer={mergedProps.getContainer}
           className={`${classPrefix}-popup`}
           maskClassName={`${classPrefix}-popup-mask`}
           bodyClassName={`${classPrefix}-popup-body`}
           style={{ top }}
           forceRender={popupForceRender}
           onMaskClick={
-            props.closeOnMaskClick
+            mergedProps.closeOnMaskClick
               ? () => {
                   changeActive(null)
                 }
diff --git a/src/components/dropdown/index.en.md b/src/components/dropdown/index.en.md
index 915555728..7d8efc45c 100644
--- a/src/components/dropdown/index.en.md
+++ b/src/components/dropdown/index.en.md
@@ -17,7 +17,7 @@ It is suitable for filtering, sorting and changing the display range or order of
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
 | activeKey | Active `Item` key | `string \| null` | - |
-| arrow | Custom arrow | `React.ReactNode` | - |
+| arrowIcon | Custom arrow icon | `React.ReactNode` | - |
 | closeOnClickAway | Whether to automatically hide after clicking outside area | `boolean` | `false` |
 | closeOnMaskClick | Whether to automatically close after clicking on the mask | `boolean` | `true` |
 | defaultActiveKey | The default active `Item` key | `string \| null` | `null` |
@@ -36,7 +36,7 @@ It is suitable for filtering, sorting and changing the display range or order of
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| arrow | Custom arrow | `React.ReactNode` | - |
+| arrowIcon | Custom arrow icon | `React.ReactNode` | - |
 | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` |
 | forceRender | Whether to render the content even if it is not active | `boolean` | `false` |
 | highlight | Highlight | `boolean` | `false` |
diff --git a/src/components/dropdown/index.zh.md b/src/components/dropdown/index.zh.md
index 8aba614b9..629128911 100644
--- a/src/components/dropdown/index.zh.md
+++ b/src/components/dropdown/index.zh.md
@@ -17,7 +17,7 @@
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
 | activeKey | 激活的 `Item` `key` | `string \| null` | - |
-| arrow | 自定义 arrow | `React.ReactNode` | - |
+| arrowIcon | 自定义箭头图标 | `React.ReactNode` | - |
 | closeOnClickAway | 是否在点击外部区域后自动隐藏 | `boolean` | `false` |
 | closeOnMaskClick | 是否在点击遮罩后自动隐藏 | `boolean` | `true` |
 | defaultActiveKey | 默认激活的 `Item` `key` | `string \| null` | `null` |
@@ -36,7 +36,7 @@
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| arrow | 自定义 arrow | `React.ReactNode` | - |
+| arrowIcon | 自定义箭头图标 | `React.ReactNode` | - |
 | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` |
 | forceRender | 被隐藏时是否渲染 `DOM` 结构 | `boolean` | `false` |
 | highlight | 高亮 | `boolean` | `false` |
diff --git a/src/components/dropdown/item.tsx b/src/components/dropdown/item.tsx
index afa8c5ff1..d71c16722 100644
--- a/src/components/dropdown/item.tsx
+++ b/src/components/dropdown/item.tsx
@@ -1,9 +1,12 @@
+import { DownFill } from 'antd-mobile-icons'
 import classNames from 'classnames'
-import React from 'react'
 import type { FC, ReactNode } from 'react'
+import React from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { useShouldRender } from '../../utils/should-render'
-import { DownFill } from 'antd-mobile-icons'
+import { mergeProp, mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
+import { IconContext } from './context'
 
 const classPrefix = `adm-dropdown-item`
 
@@ -15,27 +18,42 @@ export type DropdownItemProps = {
   forceRender?: boolean
   destroyOnClose?: boolean
   onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
+  arrowIcon?: ReactNode
+  /**
+   * @deprecated use `arrowIcon` instead
+   */
   arrow?: ReactNode
   children?: ReactNode
 } & NativeProps
 
 const Item: FC<DropdownItemProps> = props => {
+  const { dropdown: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(componentConfig, props)
+  const { active, highlight, onClick, title } = mergedProps
   const cls = classNames(classPrefix, {
-    [`${classPrefix}-active`]: props.active,
-    [`${classPrefix}-highlight`]: props.highlight ?? props.active,
+    [`${classPrefix}-active`]: active,
+    [`${classPrefix}-highlight`]: highlight ?? active,
   })
 
+  const contextArrowIcon = React.useContext(IconContext)
+  const mergedArrowIcon = mergeProp(
+    <DownFill />,
+    contextArrowIcon,
+    mergedProps.arrow,
+    mergedProps.arrowIcon
+  )
+
   return withNativeProps(
     props,
-    <div className={cls} onClick={props.onClick}>
+    <div className={cls} onClick={onClick}>
       <div className={`${classPrefix}-title`}>
-        <span className={`${classPrefix}-title-text`}>{props.title}</span>
+        <span className={`${classPrefix}-title-text`}>{title}</span>
         <span
           className={classNames(`${classPrefix}-title-arrow`, {
-            [`${classPrefix}-title-arrow-active`]: props.active,
+            [`${classPrefix}-title-arrow-active`]: active,
           })}
         >
-          {props.arrow === undefined ? <DownFill /> : props.arrow}
+          {mergedArrowIcon}
         </span>
       </div>
     </div>
diff --git a/src/components/form/form-item.tsx b/src/components/form/form-item.tsx
index 8a21f7502..e4e377544 100644
--- a/src/components/form/form-item.tsx
+++ b/src/components/form/form-item.tsx
@@ -1,20 +1,21 @@
-import React, { useContext, useCallback, useState, useRef } from 'react'
-import type { FC, ReactNode, MutableRefObject } from 'react'
+import { QuestionCircleOutline } from 'antd-mobile-icons'
 import classNames from 'classnames'
-import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { Field, FormInstance } from 'rc-field-form'
-import FieldContext from 'rc-field-form/lib/FieldContext'
 import type { FieldProps } from 'rc-field-form/lib/Field'
-import type { Meta, InternalNamePath } from 'rc-field-form/lib/interface'
-import type { FormLayout } from './index'
+import FieldContext from 'rc-field-form/lib/FieldContext'
+import type { InternalNamePath, Meta } from 'rc-field-form/lib/interface'
+import type { FC, MutableRefObject, ReactNode } from 'react'
+import React, { useCallback, useContext, useRef, useState } from 'react'
 import { devWarning } from '../../utils/dev-log'
-import { FormContext, NoStyleItemContext } from './context'
-import { toArray, isSafeSetRefComponent } from './utils'
+import { NativeProps, withNativeProps } from '../../utils/native-props'
+import { undefinedFallback } from '../../utils/undefined-fallback'
+import { mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
 import List, { ListItemProps } from '../list'
 import Popover from '../popover'
-import { QuestionCircleOutline } from 'antd-mobile-icons'
-import { useConfig } from '../config-provider'
-import { undefinedFallback } from '../../utils/undefined-fallback'
+import { FormContext, NoStyleItemContext } from './context'
+import type { FormLayout } from './index'
+import { isSafeSetRefComponent, toArray } from './utils'
 
 const NAME_SPLIT = '__SPLIT__'
 
@@ -44,10 +45,11 @@ export type FormItemProps = Pick<
 > &
   Pick<
     ListItemProps,
-    'style' | 'extra' | 'clickable' | 'arrow' | 'description'
+    'style' | 'extra' | 'clickable' | 'arrow' | 'arrowIcon' | 'description'
   > & {
     label?: ReactNode
     help?: ReactNode
+    helpIcon?: ReactNode
     hasFeedback?: boolean
     required?: boolean
     noStyle?: boolean
@@ -79,11 +81,13 @@ type FormItemLayoutProps = Pick<
   | 'disabled'
   | 'label'
   | 'help'
+  | 'helpIcon'
   | 'hidden'
   | 'layout'
   | 'extra'
   | 'clickable'
   | 'arrow'
+  | 'arrowIcon'
   | 'description'
   | 'childElementPosition'
 > & {
@@ -95,23 +99,25 @@ type FormItemLayoutProps = Pick<
 } & NativeProps
 
 const FormItemLayout: FC<FormItemLayoutProps> = props => {
+  const { locale, form: componentConfig = {} } = useConfig()
+
   const {
     style,
     extra,
     label,
     help,
+    helpIcon,
     required,
     children,
     htmlFor,
     hidden,
     arrow,
+    arrowIcon,
     childElementPosition = 'normal',
-  } = props
+  } = mergeProps(componentConfig, props)
 
   const context = useContext(FormContext)
 
-  const { locale } = useConfig()
-
   const hasFeedback =
     props.hasFeedback !== undefined ? props.hasFeedback : context.hasFeedback
   const layout = props.layout || context.layout
@@ -162,7 +168,7 @@ const FormItemLayout: FC<FormItemLayoutProps> = props => {
               e.preventDefault()
             }}
           >
-            <QuestionCircleOutline />
+            {helpIcon || <QuestionCircleOutline />}
           </span>
         </Popover>
       )}
@@ -210,7 +216,7 @@ const FormItemLayout: FC<FormItemLayoutProps> = props => {
       disabled={disabled}
       onClick={props.onClick}
       clickable={props.clickable}
-      arrow={arrow}
+      arrowIcon={arrowIcon || arrow}
     >
       <div
         className={classNames(
@@ -233,6 +239,7 @@ export const FormItem: FC<FormItemProps> = props => {
     // FormItem 相关
     label,
     help,
+    helpIcon,
     extra,
     hasFeedback,
     name,
@@ -254,6 +261,7 @@ export const FormItem: FC<FormItemProps> = props => {
     dependencies,
     clickable,
     arrow,
+    arrowIcon,
     ...fieldProps
   } = props
 
@@ -328,6 +336,7 @@ export const FormItem: FC<FormItemProps> = props => {
         label={label}
         extra={extra}
         help={help}
+        helpIcon={helpIcon}
         description={description}
         required={isRequired}
         disabled={disabled}
@@ -341,6 +350,7 @@ export const FormItem: FC<FormItemProps> = props => {
         childElementPosition={childElementPosition}
         clickable={clickable}
         arrow={arrow}
+        arrowIcon={arrowIcon}
       >
         <NoStyleItemContext.Provider value={onSubMetaChange}>
           {baseChildren}
diff --git a/src/components/form/index.en.md b/src/components/form/index.en.md
index 8c3de36a8..f540a580e 100644
--- a/src/components/form/index.en.md
+++ b/src/components/form/index.en.md
@@ -93,6 +93,7 @@ const validateMessages = {
 | getValueProps | Customize additional props with value. This prop will disable `valuePropName` | `(value) => any` | - |
 | hasFeedback | Whether to show error feedback | `boolean` | `true` |
 | help | Prompt text | `ReactNode` | - |
+| helpIcon | Prompt icon | `ReactNode` | `<QuestionCircleOutline />` |
 | hidden | Hide this field | `boolean` | `false` |
 | initialValue | Config sub default value. Form `initialValues` get higher priority when conflict. | `any` | - |
 | label | Label name | `ReactNode` | - |
diff --git a/src/components/form/index.zh.md b/src/components/form/index.zh.md
index c937dadfc..2bc3bcf00 100644
--- a/src/components/form/index.zh.md
+++ b/src/components/form/index.zh.md
@@ -93,6 +93,7 @@ const validateMessages = {
 | getValueProps | 为子元素添加额外的属性 | `(value) => any` | - |
 | hasFeedback | 是否展示错误反馈 | `boolean` | `true` |
 | help | 提示文本 | `ReactNode` | - |
+| helpIcon | 提示图标 | `ReactNode` | `<QuestionCircleOutline />` |
 | hidden | 是否隐藏整个字段 | `boolean` | `false` |
 | initialValue | 设置子元素默认值，如果与 Form 的 `initialValues` 冲突则以 Form 为准 | `any` | - |
 | label | 标签名 | `ReactNode` | - |
diff --git a/src/components/input/index.en.md b/src/components/input/index.en.md
index 5f75d954a..4a3aa1bd3 100644
--- a/src/components/input/index.en.md
+++ b/src/components/input/index.en.md
@@ -21,6 +21,7 @@ The `Input` component is layout-independent. It only includes the most basic inp
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
 | clearable | Whether to enable the clear icon, the input box will be cleared after clicking the clear icon | `boolean` | `false` |
+| clearIcon | Custom clear icon | `ReactNode` | `<CloseCircleFill />` |
 | defaultValue | The default value | `string` | - |
 | disabled | Whether it is disabled or not | `boolean` | `false` |
 | id | The `id` of the `input` element, usually used with `label` | `string` | - |
diff --git a/src/components/input/index.zh.md b/src/components/input/index.zh.md
index 72d69b76d..4f3f93781 100644
--- a/src/components/input/index.zh.md
+++ b/src/components/input/index.zh.md
@@ -21,6 +21,7 @@
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
 | clearable | 是否启用清除图标，点击清除图标后会清空输入框 | `boolean` | `false` |
+| clearIcon | 自定义清除图标 | `ReactNode` | `<CloseCircleFill />` |
 | defaultValue | 默认值 | `string` | - |
 | disabled | 是否禁用 | `boolean` | `false` |
 | id | `input` 元素的 `id`，常用来配合 `label` 使用 | `string` | - |
diff --git a/src/components/input/input.tsx b/src/components/input/input.tsx
index 7851699f6..6626f60ef 100644
--- a/src/components/input/input.tsx
+++ b/src/components/input/input.tsx
@@ -1,11 +1,17 @@
-import React, { useState, forwardRef, useImperativeHandle, useRef } from 'react'
-import { usePropsValue } from '../../utils/use-props-value'
 import { CloseCircleFill } from 'antd-mobile-icons'
-import { NativeProps, withNativeProps } from '../../utils/native-props'
-import { mergeProps } from '../../utils/with-default-props'
 import classNames from 'classnames'
+import React, {
+  ReactNode,
+  forwardRef,
+  useImperativeHandle,
+  useRef,
+  useState,
+} from 'react'
 import { bound } from '../../utils/bound'
+import { NativeProps, withNativeProps } from '../../utils/native-props'
+import { usePropsValue } from '../../utils/use-props-value'
 import { isIOS } from '../../utils/validate'
+import { mergeProps } from '../../utils/with-default-props'
 import { useConfig } from '../config-provider'
 import useInputHandleKeyDown from './useInputHandleKeyDown'
 
@@ -51,6 +57,7 @@ export type InputProps = Pick<
   defaultValue?: string
   onChange?: (val: string) => void
   clearable?: boolean
+  clearIcon?: ReactNode
   onlyShowClearWhenFocus?: boolean
   onClear?: () => void
   onEnterPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void
@@ -63,6 +70,7 @@ export type InputProps = Pick<
 
 const defaultProps = {
   defaultValue: '',
+  clearIcon: <CloseCircleFill />,
   onlyShowClearWhenFocus: true,
 }
 
@@ -73,18 +81,19 @@ export type InputRef = {
   nativeElement: HTMLInputElement | null
 }
 
-export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
-  const props = mergeProps(defaultProps, p)
-  const [value, setValue] = usePropsValue(props)
+export const Input = forwardRef<InputRef, InputProps>((props, ref) => {
+  const { locale, input: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(defaultProps, componentConfig, props)
+  const [value, setValue] = usePropsValue(mergedProps)
   const [hasFocus, setHasFocus] = useState(false)
   const compositionStartRef = useRef(false)
   const nativeInputRef = useRef<HTMLInputElement>(null)
-  const { locale } = useConfig()
+
   const handleKeydown = useInputHandleKeyDown({
-    onEnterPress: props.onEnterPress,
-    onKeyDown: props.onKeyDown,
+    onEnterPress: mergedProps.onEnterPress,
+    onKeyDown: mergedProps.onKeyDown,
     nativeInputRef,
-    enterKeyHint: props.enterKeyHint,
+    enterKeyHint: mergedProps.enterKeyHint,
   })
 
   useImperativeHandle(ref, () => ({
@@ -104,10 +113,14 @@ export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
 
   function checkValue() {
     let nextValue = value
-    if (props.type === 'number') {
+    if (mergedProps.type === 'number') {
       const boundValue =
         nextValue &&
-        bound(parseFloat(nextValue), props.min, props.max).toString()
+        bound(
+          parseFloat(nextValue),
+          mergedProps.min,
+          mergedProps.max
+        ).toString()
       // fix the display issue of numbers starting with 0
       if (Number(nextValue) !== Number(boundValue)) {
         nextValue = boundValue
@@ -119,8 +132,8 @@ export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
   }
 
   const shouldShowClear = (() => {
-    if (!props.clearable || !value || props.readOnly) return false
-    if (props.onlyShowClearWhenFocus) {
+    if (!mergedProps.clearable || !value || mergedProps.readOnly) return false
+    if (mergedProps.onlyShowClearWhenFocus) {
       return hasFocus
     } else {
       return true
@@ -128,11 +141,11 @@ export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
   })()
 
   return withNativeProps(
-    props,
+    mergedProps,
     <div
       className={classNames(
         `${classPrefix}`,
-        props.disabled && `${classPrefix}-disabled`
+        mergedProps.disabled && `${classPrefix}-disabled`
       )}
     >
       <input
@@ -144,47 +157,47 @@ export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
         }}
         onFocus={e => {
           setHasFocus(true)
-          props.onFocus?.(e)
+          mergedProps.onFocus?.(e)
         }}
         onBlur={e => {
           setHasFocus(false)
           checkValue()
-          props.onBlur?.(e)
+          mergedProps.onBlur?.(e)
         }}
-        id={props.id}
-        placeholder={props.placeholder}
-        disabled={props.disabled}
-        readOnly={props.readOnly}
-        maxLength={props.maxLength}
-        minLength={props.minLength}
-        max={props.max}
-        min={props.min}
-        autoComplete={props.autoComplete}
-        enterKeyHint={props.enterKeyHint}
-        autoFocus={props.autoFocus}
-        pattern={props.pattern}
-        inputMode={props.inputMode}
-        type={props.type}
-        name={props.name}
-        autoCapitalize={props.autoCapitalize}
-        autoCorrect={props.autoCorrect}
+        id={mergedProps.id}
+        placeholder={mergedProps.placeholder}
+        disabled={mergedProps.disabled}
+        readOnly={mergedProps.readOnly}
+        maxLength={mergedProps.maxLength}
+        minLength={mergedProps.minLength}
+        max={mergedProps.max}
+        min={mergedProps.min}
+        autoComplete={mergedProps.autoComplete}
+        enterKeyHint={mergedProps.enterKeyHint}
+        autoFocus={mergedProps.autoFocus}
+        pattern={mergedProps.pattern}
+        inputMode={mergedProps.inputMode}
+        type={mergedProps.type}
+        name={mergedProps.name}
+        autoCapitalize={mergedProps.autoCapitalize}
+        autoCorrect={mergedProps.autoCorrect}
         onKeyDown={handleKeydown}
-        onKeyUp={props.onKeyUp}
+        onKeyUp={mergedProps.onKeyUp}
         onCompositionStart={e => {
           compositionStartRef.current = true
-          props.onCompositionStart?.(e)
+          mergedProps.onCompositionStart?.(e)
         }}
         onCompositionEnd={e => {
           compositionStartRef.current = false
-          props.onCompositionEnd?.(e)
+          mergedProps.onCompositionEnd?.(e)
         }}
-        onClick={props.onClick}
-        step={props.step}
-        role={props.role}
-        aria-valuenow={props['aria-valuenow']}
-        aria-valuemax={props['aria-valuemax']}
-        aria-valuemin={props['aria-valuemin']}
-        aria-label={props['aria-label']}
+        onClick={mergedProps.onClick}
+        step={mergedProps.step}
+        role={mergedProps.role}
+        aria-valuenow={mergedProps['aria-valuenow']}
+        aria-valuemax={mergedProps['aria-valuemax']}
+        aria-valuemin={mergedProps['aria-valuemin']}
+        aria-label={mergedProps['aria-label']}
       />
       {shouldShowClear && (
         <div
@@ -194,7 +207,7 @@ export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
           }}
           onClick={() => {
             setValue('')
-            props.onClear?.()
+            mergedProps.onClear?.()
 
             // https://github.com/ant-design/ant-design-mobile/issues/5212
             if (isIOS() && compositionStartRef.current) {
@@ -204,7 +217,7 @@ export const Input = forwardRef<InputRef, InputProps>((p, ref) => {
           }}
           aria-label={locale.Input.clear}
         >
-          <CloseCircleFill />
+          {mergedProps.clearIcon}
         </div>
       )}
     </div>
diff --git a/src/components/list/index.en.md b/src/components/list/index.en.md
index 4c533b99c..ad5f9defd 100644
--- a/src/components/list/index.en.md
+++ b/src/components/list/index.en.md
@@ -56,7 +56,7 @@ Carry text, lists, pictures, paragraphs, etc. cleanly and efficiently in the for
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| arrow | Whether to show the arrow icon on the right side, also supports passing `ReactNode` to customize the icon. | `boolean \| ReactNode` | same as `clickable` |
+| arrowIcon | Whether to show the arrow icon on the right side, also supports passing `ReactNode` to customize the icon. | `boolean \| ReactNode` | same as `clickable` |
 | children | The main content area in the middle of the list item. | `ReactNode` | - |
 | clickable | Whether to show click effect. | `boolean` | Defaults to `true` when the `onClick` prop is present, otherwise defaults to `false` |
 | description | The description area in the lower middle of the list item. | `ReactNode` | - |
diff --git a/src/components/list/index.zh.md b/src/components/list/index.zh.md
index ef9fbc51e..0641cefd2 100644
--- a/src/components/list/index.zh.md
+++ b/src/components/list/index.zh.md
@@ -56,7 +56,7 @@
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| arrow | 右侧是否显示箭头图标，也支持传入 `ReactNode` 来自定义图标 | `boolean \| ReactNode` | 默认和 `clickable` 的值保持一致 |
+| arrowIcon | 右侧是否显示箭头图标，也支持传入 `ReactNode` 来自定义图标 | `boolean \| ReactNode` | 默认和 `clickable` 的值保持一致 |
 | children | 列表项中间的主内容区域 | `ReactNode` | - |
 | clickable | 是否显示点击效果 | `boolean` | 当 `onClick` 属性存在时，默认为 `true`，否则默认为 `false` |
 | description | 列表项中间下部的描述区域 | `ReactNode` | - |
diff --git a/src/components/list/list-item.tsx b/src/components/list/list-item.tsx
index d005c59ee..9ec33df94 100644
--- a/src/components/list/list-item.tsx
+++ b/src/components/list/list-item.tsx
@@ -1,9 +1,11 @@
-import React from 'react'
-import type { FC, ReactNode } from 'react'
-import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { RightOutline } from 'antd-mobile-icons'
 import classNames from 'classnames'
+import type { FC, ReactNode } from 'react'
+import React from 'react'
 import { isNodeWithContent } from '../../utils/is-node-with-content'
+import { NativeProps, withNativeProps } from '../../utils/native-props'
+import { mergeProp } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
 
 const classPrefix = `adm-list-item`
 
@@ -14,16 +16,28 @@ export type ListItemProps = {
   prefix?: ReactNode
   extra?: ReactNode
   clickable?: boolean
-  arrow?: boolean | ReactNode
+  arrowIcon?: boolean | ReactNode
   disabled?: boolean
   onClick?: (e: React.MouseEvent<HTMLElement>) => void
+  /**
+   * @deprecated use `arrowIcon` instead
+   */
+  arrow?: boolean | ReactNode
 } & NativeProps<
   '--prefix-width' | '--align-items' | '--active-background-color'
 >
 
 export const ListItem: FC<ListItemProps> = props => {
+  const { arrow, arrowIcon } = props
+  const { list: componentConfig = {} } = useConfig()
   const clickable = props.clickable ?? !!props.onClick
-  const arrow = props.arrow === undefined ? clickable : props.arrow
+
+  const showArrow = arrow ?? arrowIcon ?? clickable
+  const mergedArrowIcon = mergeProp<React.ReactNode>(
+    componentConfig.arrowIcon,
+    arrow !== true ? arrow : null,
+    arrowIcon !== true ? arrowIcon : null
+  )
 
   const content = (
     <div className={`${classPrefix}-content`}>
@@ -44,9 +58,9 @@ export const ListItem: FC<ListItemProps> = props => {
       {isNodeWithContent(props.extra) && (
         <div className={`${classPrefix}-content-extra`}>{props.extra}</div>
       )}
-      {isNodeWithContent(arrow) && (
+      {showArrow && (
         <div className={`${classPrefix}-content-arrow`}>
-          {arrow === true ? <RightOutline /> : arrow}
+          {mergedArrowIcon || <RightOutline />}
         </div>
       )}
     </div>
diff --git a/src/components/nav-bar/demos/demo1.tsx b/src/components/nav-bar/demos/demo1.tsx
index ccd25f452..d958efbbb 100644
--- a/src/components/nav-bar/demos/demo1.tsx
+++ b/src/components/nav-bar/demos/demo1.tsx
@@ -1,7 +1,7 @@
-import React from 'react'
 import { NavBar, Space, Toast } from 'antd-mobile'
+import { CloseOutline, MoreOutline, SearchOutline } from 'antd-mobile-icons'
 import { DemoBlock } from 'demos'
-import { SearchOutline, MoreOutline, CloseOutline } from 'antd-mobile-icons'
+import React from 'react'
 
 import './demo1.less'
 
@@ -34,13 +34,13 @@ export default () => {
       </DemoBlock>
 
       <DemoBlock title='返回按钮不显示图标' padding='0'>
-        <NavBar back='返回' onBack={back} backArrow={false}>
+        <NavBar back='返回' onBack={back} backIcon={false}>
           标题
         </NavBar>
       </DemoBlock>
 
       <DemoBlock title='自定义返回按钮图标' padding='0'>
-        <NavBar back='返回' backArrow={<CloseOutline />} onBack={back}>
+        <NavBar back='返回' backIcon={<CloseOutline />} onBack={back}>
           标题
         </NavBar>
       </DemoBlock>
diff --git a/src/components/nav-bar/index.en.md b/src/components/nav-bar/index.en.md
index 4c3b2a88d..4690571de 100644
--- a/src/components/nav-bar/index.en.md
+++ b/src/components/nav-bar/index.en.md
@@ -16,8 +16,8 @@ Need to display the title and action of the current page content.
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| back | The returned text of the area, if `null` returned, `backArrow` would not be rendered | `ReacNode \| null` | `''` |
-| backArrow | Whether to display the arrow of the return area, you can also pass in `ReactNode` for customization | `boolean \| ReactNode` | `true` |
+| back | The returned text of the area, if `null` returned, `backIcon` would not be rendered | `ReacNode \| null` | `''` |
+| backIcon | Whether to display the back arrow icon of the return area, you can also pass in `ReactNode` for customization | `boolean \| ReactNode` | `true` |
 | children | Title | `ReactNode` | - |
 | left | Content on the left, rendered on the right side of the return area | `ReactNode` | - |
 | onBack | Callback after clicking the return area | `() => void` | - |
diff --git a/src/components/nav-bar/index.zh.md b/src/components/nav-bar/index.zh.md
index 40f030c67..18ca1c4d2 100644
--- a/src/components/nav-bar/index.zh.md
+++ b/src/components/nav-bar/index.zh.md
@@ -16,8 +16,8 @@
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| back | 返回区域的文字，如果为 `null` 的话，`backArrow` 也不会渲染 | `ReactNode \| null` | `''` |
-| backArrow | 是否显示返回区域的箭头，也可以传入 `ReactNode` 进行自定义 | `boolean \| ReactNode` | `true` |
+| back | 返回区域的文字，如果为 `null` 的话，`backIcon` 也不会渲染 | `ReactNode \| null` | `''` |
+| backIcon | 是否显示返回区域的箭头，也可以传入 `ReactNode` 进行自定义 | `boolean \| ReactNode` | `true` |
 | children | 标题 | `ReactNode` | - |
 | left | 左侧内容，渲染在返回区域的右侧 | `ReactNode` | - |
 | onBack | 点击返回区域后的回调 | `() => void` | - |
diff --git a/src/components/nav-bar/nav-bar.tsx b/src/components/nav-bar/nav-bar.tsx
index 81ff6ef9c..79baa5abf 100644
--- a/src/components/nav-bar/nav-bar.tsx
+++ b/src/components/nav-bar/nav-bar.tsx
@@ -1,14 +1,19 @@
-import React from 'react'
-import type { FC, ReactNode } from 'react'
-import classNames from 'classnames'
 import { LeftOutline } from 'antd-mobile-icons'
+import classNames from 'classnames'
+import type { FC, ReactNode } from 'react'
+import React from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
 
 const classPrefix = `adm-nav-bar`
 
 export type NavBarProps = {
   back?: ReactNode
+  backIcon?: boolean | ReactNode
+  /**
+   * @deprecated use `backIcon` instead
+   */
   backArrow?: boolean | ReactNode
   left?: ReactNode
   right?: ReactNode
@@ -17,30 +22,34 @@ export type NavBarProps = {
 } & NativeProps<'--height' | '--border-bottom'>
 
 const defaultProps = {
-  backArrow: true,
+  backIcon: true,
 }
-export const NavBar: FC<NavBarProps> = p => {
-  const props = mergeProps(defaultProps, p)
-  const { back, backArrow } = props
+
+export const NavBar: FC<NavBarProps> = props => {
+  const { navBar: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(defaultProps, componentConfig, props)
+  const { back, backIcon, backArrow } = mergedProps
 
   return withNativeProps(
-    props,
+    mergedProps,
     <div className={classNames(classPrefix)}>
       <div className={`${classPrefix}-left`} role='button'>
         {back !== null && (
-          <div className={`${classPrefix}-back`} onClick={props.onBack}>
-            {backArrow && (
+          <div className={`${classPrefix}-back`} onClick={mergedProps.onBack}>
+            {(backIcon || backArrow) && (
               <span className={`${classPrefix}-back-arrow`}>
-                {backArrow === true ? <LeftOutline /> : backArrow}
+                {backIcon === true || backArrow === true
+                  ? componentConfig.backIcon || <LeftOutline />
+                  : backIcon || backArrow}
               </span>
             )}
             <span aria-hidden='true'>{back}</span>
           </div>
         )}
-        {props.left}
+        {mergedProps.left}
       </div>
-      <div className={`${classPrefix}-title`}>{props.children}</div>
-      <div className={`${classPrefix}-right`}>{props.right}</div>
+      <div className={`${classPrefix}-title`}>{mergedProps.children}</div>
+      <div className={`${classPrefix}-right`}>{mergedProps.right}</div>
     </div>
   )
 }
diff --git a/src/components/notice-bar/index.en.md b/src/components/notice-bar/index.en.md
index ab0c30fa8..4149d5487 100644
--- a/src/components/notice-bar/index.en.md
+++ b/src/components/notice-bar/index.en.md
@@ -17,6 +17,7 @@ It is applicable to the notification of information in the current page, which i
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
 | closeable | Whether it can be closed | `boolean` | `false` |
+| closeIcon | Custom close button icon | `ReactNode` | `<CloseOutline />` |
 | color | The type of the NoticeBar | `'default' \| 'alert' \| 'error' \| 'info'` | `'default'` |
 | content | The content of the NoticeBar | `React.ReactNode` | - |
 | delay | Delay to start scrolling, unit `ms` | `number` | `2000` |
diff --git a/src/components/notice-bar/index.zh.md b/src/components/notice-bar/index.zh.md
index 65f5d0a24..14d5407e4 100644
--- a/src/components/notice-bar/index.zh.md
+++ b/src/components/notice-bar/index.zh.md
@@ -17,6 +17,7 @@
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
 | closeable | 是否可关闭 | `boolean` | `false` |
+| closeIcon | 自定义关闭按钮图标 | `ReactNode` | `<CloseOutline />` |
 | color | 通告栏的类型 | `'default' \| 'alert' \| 'error' \| 'info'` | `'default'` |
 | content | 公告内容 | `React.ReactNode` | - |
 | delay | 开始滚动的延迟，单位 `ms` | `number` | `2000` |
diff --git a/src/components/notice-bar/notice-bar.less b/src/components/notice-bar/notice-bar.less
index d23d83384..13ef66513 100644
--- a/src/components/notice-bar/notice-bar.less
+++ b/src/components/notice-bar/notice-bar.less
@@ -79,9 +79,6 @@
     display: flex;
     align-items: center;
     justify-content: center;
-  }
-
-  &-close-icon {
     font-size: var(--adm-font-size-10);
   }
 
diff --git a/src/components/notice-bar/notice-bar.tsx b/src/components/notice-bar/notice-bar.tsx
index 655cb462c..b2a858a3e 100644
--- a/src/components/notice-bar/notice-bar.tsx
+++ b/src/components/notice-bar/notice-bar.tsx
@@ -1,12 +1,13 @@
-import React, { useState, useRef, memo } from 'react'
-import type { ReactNode } from 'react'
-import classNames from 'classnames'
-import { CloseOutline, SoundOutline } from 'antd-mobile-icons'
 import { useTimeout } from 'ahooks'
-import { mergeProps } from '../../utils/with-default-props'
+import { CloseOutline, SoundOutline } from 'antd-mobile-icons'
+import classNames from 'classnames'
+import type { ReactNode } from 'react'
+import React, { memo, useRef, useState } from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
-import { useResizeEffect } from '../../utils/use-resize-effect'
 import { useMutationEffect } from '../../utils/use-mutation-effect'
+import { useResizeEffect } from '../../utils/use-resize-effect'
+import { mergeProp, mergeProps } from '../../utils/with-default-props'
+import { useConfig } from '../config-provider'
 
 const classPrefix = `adm-notice-bar`
 
@@ -21,6 +22,8 @@ export type NoticeBarProps = {
   content: ReactNode
   /** Whether it can be closed */
   closeable?: boolean
+  /** Custom close icon */
+  closeIcon?: ReactNode
   /** Callback when closed */
   onClose?: () => void
   /** Event when click */
@@ -44,25 +47,31 @@ const defaultProps = {
   color: 'default',
   delay: 2000,
   speed: 50,
-  wrap: false,
   icon: <SoundOutline />,
+  wrap: false,
 }
 
-export const NoticeBar = memo<NoticeBarProps>(p => {
-  const props = mergeProps(defaultProps, p)
+export const NoticeBar = memo<NoticeBarProps>(props => {
+  const { noticeBar: componentConfig = {} } = useConfig()
+  const mergedProps = mergeProps(defaultProps, componentConfig, props)
+  const closeIcon = mergeProp(
+    <CloseOutline className={`${classPrefix}-close-icon`} />,
+    componentConfig.closeIcon,
+    props.closeIcon
+  )
 
   const containerRef = useRef<HTMLSpanElement>(null)
   const textRef = useRef<HTMLSpanElement>(null)
 
   const [visible, setVisible] = useState(true)
 
-  const speed = props.speed
+  const speed = mergedProps.speed
 
   const delayLockRef = useRef(true)
   const animatingRef = useRef(false)
 
   function start() {
-    if (delayLockRef.current || props.wrap) return
+    if (delayLockRef.current || mergedProps.wrap) return
 
     const container = containerRef.current
     const text = textRef.current
@@ -95,7 +104,7 @@ export const NoticeBar = memo<NoticeBarProps>(p => {
   useTimeout(() => {
     delayLockRef.current = false
     start()
-  }, props.delay)
+  }, mergedProps.delay)
 
   useResizeEffect(() => {
     start()
@@ -116,15 +125,19 @@ export const NoticeBar = memo<NoticeBarProps>(p => {
   if (!visible) return null
 
   return withNativeProps(
-    props,
+    mergedProps,
     <div
-      className={classNames(classPrefix, `${classPrefix}-${props.color}`, {
-        [`${classPrefix}-wrap`]: props.wrap,
-      })}
-      onClick={props.onClick}
+      className={classNames(
+        classPrefix,
+        `${classPrefix}-${mergedProps.color}`,
+        {
+          [`${classPrefix}-wrap`]: mergedProps.wrap,
+        }
+      )}
+      onClick={mergedProps.onClick}
     >
-      {props.icon && (
-        <span className={`${classPrefix}-left`}>{props.icon}</span>
+      {mergedProps.icon && (
+        <span className={`${classPrefix}-left`}>{mergedProps.icon}</span>
       )}
       <span ref={containerRef} className={`${classPrefix}-content`}>
         <span
@@ -135,21 +148,21 @@ export const NoticeBar = memo<NoticeBarProps>(p => {
           ref={textRef}
           className={`${classPrefix}-content-inner`}
         >
-          {props.content}
+          {mergedProps.content}
         </span>
       </span>
-      {(props.closeable || props.extra) && (
+      {(mergedProps.closeable || mergedProps.extra) && (
         <span className={`${classPrefix}-right`}>
-          {props.extra}
-          {props.closeable && (
+          {mergedProps.extra}
+          {mergedProps.closeable && (
             <div
               className={`${classPrefix}-close`}
               onClick={() => {
                 setVisible(false)
-                props.onClose?.()
+                mergedProps.onClose?.()
               }}
             >
-              <CloseOutline className={`${classPrefix}-close-icon`} />
+              {closeIcon}
             </div>
           )}
         </span>
diff --git a/src/components/popup/index.en.md b/src/components/popup/index.en.md
index 33bc6e6cd..35fc73e69 100644
--- a/src/components/popup/index.en.md
+++ b/src/components/popup/index.en.md
@@ -24,6 +24,7 @@ It is suitable for displaying pop-up windows, information prompts, selection inp
 | bodyStyle | Content section style | `React.CSSProperties` | - |
 | className | Container class name | `string` | - |
 | closeOnMaskClick | Whether to close after clicking the mask layer | `boolean` | `false` |
+| closeIcon | Custom close button icon | `ReactNode` | `<CloseOutline/>` |
 | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` |
 | forceRender | Render content forcely | `boolean` | `false` |
 | getContainer | To get the specified mounted `HTML` node, the default is `body`, if `null` returned, it would be rendered to the current node | `HTMLElement \| () => HTMLElement \| null` | `() => document.body` |
diff --git a/src/components/popup/index.zh.md b/src/components/popup/index.zh.md
index 6c699b774..88f9f7ad5 100644
--- a/src/components/popup/index.zh.md
+++ b/src/components/popup/index.zh.md
@@ -24,6 +24,7 @@
 | bodyStyle | 内容区域样式 | `React.CSSProperties` | - |
 | className | 容器类名 | `string` | - |
 | closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `false` |
+| closeIcon | 自定义关闭按钮图标 | `ReactNode` | `<CloseOutline/>` |
 | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` |
 | forceRender | 强制渲染内容 | `boolean` | `false` |
 | getContainer | 指定挂载的 `HTML` 节点，默认为 `body`，如果为 `null` 的话，会渲染到当前节点 | `HTMLElement \| () => HTMLElement \| null` | `() => document.body` |
diff --git a/src/components/popup/popup-base-props.ts b/src/components/popup/popup-base-props.tsx
similarity index 86%
rename from src/components/popup/popup-base-props.ts
rename to src/components/popup/popup-base-props.tsx
index dc3817366..5c47b61f3 100644
--- a/src/components/popup/popup-base-props.ts
+++ b/src/components/popup/popup-base-props.tsx
@@ -1,8 +1,8 @@
-import React from 'react'
-import type { CSSProperties } from 'react'
+import React, { CSSProperties, ReactNode } from 'react'
 import { GetContainer } from '../../utils/render-to-container'
 import { MaskProps } from '../mask'
 import { PropagationEvent } from '../../utils/with-stop-propagation'
+import { CloseOutline } from 'antd-mobile-icons'
 
 export type PopupBaseProps = {
   afterClose?: () => void
@@ -10,6 +10,7 @@ export type PopupBaseProps = {
   bodyClassName?: string
   bodyStyle?: CSSProperties
   closeOnMaskClick?: boolean
+  closeIcon?: ReactNode
   destroyOnClose?: boolean
   disableBodyScroll?: boolean
   forceRender?: boolean
@@ -27,6 +28,7 @@ export type PopupBaseProps = {
 
 export const defaultPopupBaseProps = {
   closeOnMaskClick: false,
+  closeIcon: <CloseOutline />,
   destroyOnClose: false,
   disableBodyScroll: true,
   forceRender: false,
diff --git a/src/components/popup/popup.tsx b/src/components/popup/popup.tsx
index 45bb0570e..c736cb0b0 100644
--- a/src/components/popup/popup.tsx
+++ b/src/components/popup/popup.tsx
@@ -10,7 +10,6 @@ import { renderToContainer } from '../../utils/render-to-container'
 import { useSpring, animated } from '@react-spring/web'
 import { withStopPropagation } from '../../utils/with-stop-propagation'
 import { ShouldRender } from '../../utils/should-render'
-import { CloseOutline } from 'antd-mobile-icons'
 import { defaultPopupBaseProps, PopupBaseProps } from './popup-base-props'
 import { useInnerVisible } from '../../utils/use-inner-visible'
 import { useConfig } from '../config-provider'
@@ -32,7 +31,8 @@ const defaultProps = {
 }
 
 export const Popup: FC<PopupProps> = p => {
-  const props = mergeProps(defaultProps, p)
+  const { locale, popup: componentConfig = {} } = useConfig()
+  const props = mergeProps(defaultProps, componentConfig, p)
 
   const bodyCls = classNames(
     `${classPrefix}-body`,
@@ -40,7 +40,6 @@ export const Popup: FC<PopupProps> = p => {
     `${classPrefix}-body-position-${props.position}`
   )
 
-  const { locale } = useConfig()
   const [active, setActive] = useState(props.visible)
   const ref = useRef<HTMLDivElement>(null)
   useLockScroll(ref, props.disableBodyScroll && active ? 'strict' : false)
@@ -156,7 +155,7 @@ export const Popup: FC<PopupProps> = p => {
               role='button'
               aria-label={locale.common.close}
             >
-              <CloseOutline />
+              {props.closeIcon}
             </a>
           )}
           {props.children}
diff --git a/src/components/result-page/result-page.tsx b/src/components/result-page/result-page.tsx
index 34b8e4a70..71acddea8 100644
--- a/src/components/result-page/result-page.tsx
+++ b/src/components/result-page/result-page.tsx
@@ -1,29 +1,14 @@
 import React, { useState } from 'react'
 import type { FC, ReactNode } from 'react'
-
-import {
-  CheckCircleFill,
-  CloseCircleFill,
-  InformationCircleFill,
-  ClockCircleFill,
-  ExclamationCircleFill,
-} from 'antd-mobile-icons'
 import classNames from 'classnames'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { mergeProps } from '../../utils/with-default-props'
 import { isNodeWithContent } from '../../utils/is-node-with-content'
 import Button from '../button'
+import { useResultIcon } from '../result/use-result-icon'
 
 const classPrefix = `adm-result-page`
 
-const iconRecord = {
-  success: CheckCircleFill,
-  error: CloseCircleFill,
-  info: InformationCircleFill,
-  waiting: ClockCircleFill,
-  warning: ExclamationCircleFill,
-}
-
 interface ResultPageDetail {
   label: ReactNode
   value: ReactNode
@@ -67,7 +52,7 @@ export const ResultPage: FC<ResultPageProps> = p => {
     onPrimaryButtonClick,
     onSecondaryButtonClick,
   } = props
-  const resultIcon = icon || React.createElement(iconRecord[status])
+  const fallbackIcon = useResultIcon(status)
 
   const [collapse, setCollapse] = useState(true)
 
@@ -78,7 +63,7 @@ export const ResultPage: FC<ResultPageProps> = p => {
     props,
     <div className={classPrefix}>
       <div className={`${classPrefix}-header`}>
-        <div className={`${classPrefix}-icon`}>{resultIcon}</div>
+        <div className={`${classPrefix}-icon`}>{icon || fallbackIcon}</div>
         <div className={`${classPrefix}-title`}>{title}</div>
         {isNodeWithContent(description) ? (
           <div className={`${classPrefix}-description`}>{description}</div>
diff --git a/src/components/result/result.tsx b/src/components/result/result.tsx
index 32041b9be..ffe6da9d8 100644
--- a/src/components/result/result.tsx
+++ b/src/components/result/result.tsx
@@ -1,26 +1,12 @@
 import React from 'react'
 import type { FC, ReactNode } from 'react'
 import classNames from 'classnames'
-import {
-  CheckCircleFill,
-  CloseCircleFill,
-  InformationCircleFill,
-  ClockCircleFill,
-  ExclamationCircleFill,
-} from 'antd-mobile-icons'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { mergeProps } from '../../utils/with-default-props'
+import { useResultIcon } from './use-result-icon'
 
 const classPrefix = `adm-result`
 
-const iconRecord = {
-  success: CheckCircleFill,
-  error: CloseCircleFill,
-  info: InformationCircleFill,
-  waiting: ClockCircleFill,
-  warning: ExclamationCircleFill,
-}
-
 const defaultProps = {
   status: 'info',
 }
@@ -35,13 +21,13 @@ export type ResultProps = {
 export const Result: FC<ResultProps> = p => {
   const props = mergeProps(defaultProps, p)
   const { status, title, description, icon } = props
+  const fallbackIcon = useResultIcon(status)
   if (!status) return null
-  const resultIcon = icon || React.createElement(iconRecord[status])
 
   return withNativeProps(
     props,
     <div className={classNames(classPrefix, `${classPrefix}-${status}`)}>
-      <div className={`${classPrefix}-icon`}>{resultIcon}</div>
+      <div className={`${classPrefix}-icon`}>{icon || fallbackIcon}</div>
       <div className={`${classPrefix}-title`}>{title}</div>
       {!!description && (
         <div className={`${classPrefix}-description`}>{description}</div>
diff --git a/src/components/result/use-result-icon.tsx b/src/components/result/use-result-icon.tsx
new file mode 100644
index 000000000..c98e2ded9
--- /dev/null
+++ b/src/components/result/use-result-icon.tsx
@@ -0,0 +1,38 @@
+import React from 'react'
+import {
+  CheckCircleFill,
+  CloseCircleFill,
+  InformationCircleFill,
+  ClockCircleFill,
+  ExclamationCircleFill,
+} from 'antd-mobile-icons'
+import { useConfig } from '../config-provider'
+
+export type ResultIconProps = {}
+
+export const useResultIcon = (
+  status?: 'success' | 'error' | 'info' | 'waiting' | 'warning'
+) => {
+  const { result: componentConfig = {} } = useConfig()
+  const {
+    successIcon = <CheckCircleFill />,
+    errorIcon = <CloseCircleFill />,
+    infoIcon = <InformationCircleFill />,
+    waitingIcon = <ClockCircleFill />,
+    warningIcon = <ExclamationCircleFill />,
+  } = componentConfig || {}
+  switch (status) {
+    case 'success':
+      return successIcon
+    case 'error':
+      return errorIcon
+    case 'info':
+      return infoIcon
+    case 'waiting':
+      return waitingIcon
+    case 'warning':
+      return warningIcon
+    default:
+      return null
+  }
+}
diff --git a/src/components/search-bar/index.en.md b/src/components/search-bar/index.en.md
index dad981d8b..ca7c5877c 100644
--- a/src/components/search-bar/index.en.md
+++ b/src/components/search-bar/index.en.md
@@ -21,7 +21,7 @@ Narrow down the information pool to get targeted information quickly and easily.
 | cancelText | Text of the cancel button | `string` | `'取消'` |
 | clearOnCancel | Whether to clear the input after the cancel button is clicked | `boolean` | `true` |
 | clearable | Whether to enable the clear icon, the input would be cleared after the clear icon is clicked | `boolean` | `true` |
-| icon | Icon | `ReactNode` | `<SearchOutline />` |
+| searchIcon | Custom search icon | `ReactNode` | `<SearchOutline />` |
 | maxLength | The maximum number of characters the user can enter | `number` | - |
 | onBlur | Triggered when the input lose focus | `(e: React.FocusEvent<HTMLInputElement>) => void` | - |
 | onCancel | Triggered when the cancel button is clicked | `() => void` | - |
diff --git a/src/components/search-bar/index.zh.md b/src/components/search-bar/index.zh.md
index c0998f90b..de6a44049 100644
--- a/src/components/search-bar/index.zh.md
+++ b/src/components/search-bar/index.zh.md
@@ -21,7 +21,7 @@
 | cancelText | 取消按钮的文案 | `string` | `'取消'` |
 | clearOnCancel | 点击取消按钮后是否清空输入框 | `boolean` | `true` |
 | clearable | 是否启用清除图标，点击清除图标后会清空输入框 | `boolean` | `true` |
-| icon | 图标 | `ReactNode` | `<SearchOutline />` |
+| searchIcon | 自定义搜索图标 | `ReactNode` | `<SearchOutline />` |
 | maxLength | 输入的最大字符数 | `number` | - |
 | onBlur | 输入框失去焦点时触发 | `(e: React.FocusEvent<HTMLInputElement>) => void` | - |
 | onCancel | 点击取消按钮时触发 | `() => void` | - |
diff --git a/src/components/search-bar/search-bar.tsx b/src/components/search-bar/search-bar.tsx
index 0614f7207..3ebca2a06 100644
--- a/src/components/search-bar/search-bar.tsx
+++ b/src/components/search-bar/search-bar.tsx
@@ -1,13 +1,13 @@
-import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react'
-import type { ReactNode } from 'react'
+import { SearchOutline } from 'antd-mobile-icons'
 import classNames from 'classnames'
-import Input, { InputRef, InputProps } from '../input'
-import Button from '../button'
+import type { ReactNode } from 'react'
+import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
-import { mergeProps } from '../../utils/with-default-props'
-import { SearchOutline } from 'antd-mobile-icons'
 import { usePropsValue } from '../../utils/use-props-value'
+import { mergeProp, mergeProps } from '../../utils/with-default-props'
+import Button from '../button'
 import { useConfig } from '../config-provider'
+import Input, { InputProps, InputRef } from '../input'
 
 const classPrefix = `adm-search-bar`
 
@@ -25,6 +25,10 @@ export type SearchBarProps = Pick<
   onlyShowClearWhenFocus?: boolean
   showCancelButton?: boolean | ((focus: boolean, value: string) => boolean)
   cancelText?: string
+  searchIcon?: ReactNode
+  /**
+   * @deprecated use `searchIcon` instead
+   */
   icon?: ReactNode
   clearOnCancel?: boolean
   onSearch?: (val: string) => void
@@ -44,116 +48,124 @@ const defaultProps = {
   showCancelButton: false as NonNullable<SearchBarProps['showCancelButton']>,
   defaultValue: '',
   clearOnCancel: true,
-  icon: <SearchOutline />,
 }
 
-export const SearchBar = forwardRef<SearchBarRef, SearchBarProps>((p, ref) => {
-  const { locale } = useConfig()
-  const props = mergeProps(
-    defaultProps,
-    {
-      cancelText: locale.common.cancel,
-    },
-    p
-  )
-  const [value, setValue] = usePropsValue(props)
-  const [hasFocus, setHasFocus] = useState(false)
-  const inputRef = useRef<InputRef>(null)
-  const composingRef = useRef(false)
+export const SearchBar = forwardRef<SearchBarRef, SearchBarProps>(
+  (props, ref) => {
+    const { locale, searchBar: componentConfig = {} } = useConfig()
+    const mergedProps = mergeProps(
+      defaultProps,
+      componentConfig,
+      {
+        cancelText: locale.common.cancel,
+      },
+      props
+    )
+    const searchIcon = mergeProp(
+      <SearchOutline />,
+      componentConfig.searchIcon,
+      props.icon,
+      props.searchIcon
+    )
+    const [value, setValue] = usePropsValue(mergedProps)
+    const [hasFocus, setHasFocus] = useState(false)
+    const inputRef = useRef<InputRef>(null)
+    const composingRef = useRef(false)
+
+    useImperativeHandle(ref, () => ({
+      clear: () => inputRef.current?.clear(),
+      focus: () => inputRef.current?.focus(),
+      blur: () => inputRef.current?.blur(),
+      get nativeElement() {
+        return inputRef.current?.nativeElement ?? null
+      },
+    }))
 
-  useImperativeHandle(ref, () => ({
-    clear: () => inputRef.current?.clear(),
-    focus: () => inputRef.current?.focus(),
-    blur: () => inputRef.current?.blur(),
-    get nativeElement() {
-      return inputRef.current?.nativeElement ?? null
-    },
-  }))
+    const renderCancelButton = () => {
+      let isShowCancel: boolean
 
-  const renderCancelButton = () => {
-    let isShowCancel: boolean
+      if (typeof mergedProps.showCancelButton === 'function') {
+        isShowCancel = mergedProps.showCancelButton(hasFocus, value)
+      } else {
+        isShowCancel = mergedProps.showCancelButton && hasFocus
+      }
 
-    if (typeof props.showCancelButton === 'function') {
-      isShowCancel = props.showCancelButton(hasFocus, value)
-    } else {
-      isShowCancel = props.showCancelButton && hasFocus
+      return (
+        isShowCancel && (
+          <div className={`${classPrefix}-suffix`}>
+            <Button
+              fill='none'
+              className={`${classPrefix}-cancel-button`}
+              onClick={() => {
+                if (mergedProps.clearOnCancel) {
+                  inputRef.current?.clear()
+                }
+                inputRef.current?.blur()
+                mergedProps.onCancel?.()
+              }}
+              onMouseDown={e => {
+                e.preventDefault()
+              }}
+            >
+              {mergedProps.cancelText}
+            </Button>
+          </div>
+        )
+      )
     }
 
-    return (
-      isShowCancel && (
-        <div className={`${classPrefix}-suffix`}>
-          <Button
-            fill='none'
-            className={`${classPrefix}-cancel-button`}
-            onClick={() => {
-              if (props.clearOnCancel) {
-                inputRef.current?.clear()
+    return withNativeProps(
+      mergedProps,
+      <div
+        className={classNames(classPrefix, {
+          [`${classPrefix}-active`]: hasFocus,
+        })}
+      >
+        <div className={`${classPrefix}-input-box`}>
+          {searchIcon && (
+            <div className={`${classPrefix}-input-box-icon`}>{searchIcon}</div>
+          )}
+          <Input
+            ref={inputRef}
+            className={classNames(`${classPrefix}-input`, {
+              [`${classPrefix}-input-without-icon`]: !searchIcon,
+            })}
+            value={value}
+            onChange={setValue}
+            maxLength={mergedProps.maxLength}
+            placeholder={mergedProps.placeholder}
+            clearable={mergedProps.clearable}
+            onlyShowClearWhenFocus={mergedProps.onlyShowClearWhenFocus}
+            onFocus={e => {
+              setHasFocus(true)
+              mergedProps.onFocus?.(e)
+            }}
+            onBlur={e => {
+              setHasFocus(false)
+              mergedProps.onBlur?.(e)
+            }}
+            onClear={mergedProps.onClear}
+            type='search'
+            enterKeyHint='search'
+            onEnterPress={() => {
+              if (!composingRef.current) {
+                inputRef.current?.blur()
+                mergedProps.onSearch?.(value)
               }
-              inputRef.current?.blur()
-              props.onCancel?.()
             }}
-            onMouseDown={e => {
-              e.preventDefault()
+            aria-label={locale.SearchBar.name}
+            onCompositionStart={e => {
+              composingRef.current = true
+              mergedProps.onCompositionStart?.(e)
+            }}
+            onCompositionEnd={e => {
+              composingRef.current = false
+              mergedProps.onCompositionEnd?.(e)
             }}
-          >
-            {props.cancelText}
-          </Button>
+          />
         </div>
-      )
+        {renderCancelButton()}
+      </div>
     )
   }
-
-  return withNativeProps(
-    props,
-    <div
-      className={classNames(classPrefix, {
-        [`${classPrefix}-active`]: hasFocus,
-      })}
-    >
-      <div className={`${classPrefix}-input-box`}>
-        {props.icon && (
-          <div className={`${classPrefix}-input-box-icon`}>{props.icon}</div>
-        )}
-        <Input
-          ref={inputRef}
-          className={classNames(`${classPrefix}-input`, {
-            [`${classPrefix}-input-without-icon`]: !props.icon,
-          })}
-          value={value}
-          onChange={setValue}
-          maxLength={props.maxLength}
-          placeholder={props.placeholder}
-          clearable={props.clearable}
-          onlyShowClearWhenFocus={props.onlyShowClearWhenFocus}
-          onFocus={e => {
-            setHasFocus(true)
-            props.onFocus?.(e)
-          }}
-          onBlur={e => {
-            setHasFocus(false)
-            props.onBlur?.(e)
-          }}
-          onClear={props.onClear}
-          type='search'
-          enterKeyHint='search'
-          onEnterPress={() => {
-            if (!composingRef.current) {
-              inputRef.current?.blur()
-              props.onSearch?.(value)
-            }
-          }}
-          aria-label={locale.SearchBar.name}
-          onCompositionStart={e => {
-            composingRef.current = true
-            props.onCompositionStart?.(e)
-          }}
-          onCompositionEnd={e => {
-            composingRef.current = false
-            props.onCompositionEnd?.(e)
-          }}
-        />
-      </div>
-      {renderCancelButton()}
-    </div>
-  )
-})
+)
diff --git a/src/components/toast/toast.less b/src/components/toast/toast.less
index 7a23a018a..05871a9b8 100644
--- a/src/components/toast/toast.less
+++ b/src/components/toast/toast.less
@@ -30,7 +30,7 @@
   }
   .@{class-prefix-toast}-main-text {
     padding: 12px;
-    min-width: 0px;
+    min-width: 0;
   }
   .@{class-prefix-toast}-main-icon {
     padding: 35px 12px;
diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx
index b9e23ac7a..fe7a7cbdc 100644
--- a/src/components/toast/toast.tsx
+++ b/src/components/toast/toast.tsx
@@ -1,13 +1,13 @@
-import React, { useMemo } from 'react'
-import type { FC, ReactNode } from 'react'
-import classNames from 'classnames'
 import { CheckOutline, CloseOutline } from 'antd-mobile-icons'
-import Mask from '../mask'
-import type { MaskProps } from '../mask'
+import classNames from 'classnames'
+import type { FC, ReactNode } from 'react'
+import React, { useMemo } from 'react'
+import { GetContainer } from '../../utils/render-to-container'
 import { mergeProps } from '../../utils/with-default-props'
 import { PropagationEvent } from '../../utils/with-stop-propagation'
-import { GetContainer } from '../../utils/render-to-container'
 import AutoCenter from '../auto-center'
+import type { MaskProps } from '../mask'
+import Mask from '../mask'
 import SpinLoading from '../spin-loading'
 
 const classPrefix = `adm-toast`
diff --git a/src/components/virtual-input/virtual-input.tsx b/src/components/virtual-input/virtual-input.tsx
index 955496f4b..0d5f5a702 100644
--- a/src/components/virtual-input/virtual-input.tsx
+++ b/src/components/virtual-input/virtual-input.tsx
@@ -1,3 +1,7 @@
+import { useIsomorphicLayoutEffect } from 'ahooks'
+import { CloseCircleFill } from 'antd-mobile-icons'
+import classNames from 'classnames'
+import type { ReactElement } from 'react'
 import React, {
   forwardRef,
   useEffect,
@@ -5,16 +9,12 @@ import React, {
   useRef,
   useState,
 } from 'react'
-import type { ReactElement } from 'react'
-import type { InputProps } from '../input'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
-import { mergeProps } from '../../utils/with-default-props'
-import { NumberKeyboardProps } from '../number-keyboard'
 import { usePropsValue } from '../../utils/use-props-value'
-import classNames from 'classnames'
-import { CloseCircleFill } from 'antd-mobile-icons'
-import { useIsomorphicLayoutEffect } from 'ahooks'
+import { mergeProp, mergeProps } from '../../utils/with-default-props'
 import { useConfig } from '../config-provider'
+import type { InputProps } from '../input'
+import { NumberKeyboardProps } from '../number-keyboard'
 
 const classPrefix = 'adm-virtual-input'
 
@@ -25,7 +25,10 @@ export type VirtualInputProps = {
   keyboard?: ReactElement<NumberKeyboardProps>
   clearable?: boolean
   onClear?: () => void
-} & Pick<InputProps, 'value' | 'onChange' | 'placeholder' | 'disabled'> &
+} & Pick<
+  InputProps,
+  'value' | 'onChange' | 'placeholder' | 'disabled' | 'clearIcon'
+> &
   NativeProps<
     | '--font-size'
     | '--color'
@@ -46,13 +49,19 @@ export type VirtualInputRef = {
 }
 
 export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
-  (p, ref) => {
-    const props = mergeProps(defaultProps, p)
-    const [value, setValue] = usePropsValue(props)
+  (props, ref) => {
+    const { locale, input: componentConfig = {} } = useConfig()
+    const mergedProps = mergeProps(defaultProps, componentConfig, props)
+    const [value, setValue] = usePropsValue(mergedProps)
     const rootRef = useRef<HTMLDivElement>(null)
     const contentRef = useRef<HTMLDivElement>(null)
     const [hasFocus, setHasFocus] = useState(false)
-    const { locale } = useConfig()
+
+    const clearIcon = mergeProp(
+      <CloseCircleFill />,
+      componentConfig.clearIcon,
+      props.clearIcon
+    )
 
     function scrollToEnd() {
       const root = rootRef.current
@@ -85,15 +94,15 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
 
     function onFocus() {
       setHasFocus(true)
-      props.onFocus?.()
+      mergedProps.onFocus?.()
     }
 
     function onBlur() {
       setHasFocus(false)
-      props.onBlur?.()
+      mergedProps.onBlur?.()
     }
 
-    const keyboard = props.keyboard
+    const keyboard = mergedProps.keyboard
     const keyboardElement =
       keyboard &&
       React.cloneElement(keyboard, {
@@ -123,46 +132,46 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
       } as NumberKeyboardProps)
 
     return withNativeProps(
-      props,
+      mergedProps,
       <div
         ref={rootRef}
         className={classNames(classPrefix, {
-          [`${classPrefix}-disabled`]: props.disabled,
+          [`${classPrefix}-disabled`]: mergedProps.disabled,
         })}
-        tabIndex={props.disabled ? undefined : 0}
+        tabIndex={mergedProps.disabled ? undefined : 0}
         role='textbox'
         onFocus={onFocus}
         onBlur={onBlur}
-        onClick={props.onClick}
+        onClick={mergedProps.onClick}
       >
         <div
           className={`${classPrefix}-content`}
           ref={contentRef}
-          aria-disabled={props.disabled}
-          aria-label={props.placeholder}
+          aria-disabled={mergedProps.disabled}
+          aria-label={mergedProps.placeholder}
         >
           {value}
           <div className={`${classPrefix}-caret-container`}>
             {hasFocus && <div className={`${classPrefix}-caret`} />}
           </div>
         </div>
-        {props.clearable && !!value && hasFocus && (
+        {mergedProps.clearable && !!value && hasFocus && (
           <div
             className={`${classPrefix}-clear`}
             onClick={e => {
               e.stopPropagation()
               setValue('')
-              props.onClear?.()
+              mergedProps.onClear?.()
             }}
             role='button'
             aria-label={locale.Input.clear}
           >
-            <CloseCircleFill />
+            {clearIcon}
           </div>
         )}
         {[undefined, null, ''].includes(value) && (
           <div className={`${classPrefix}-placeholder`}>
-            {props.placeholder}
+            {mergedProps.placeholder}
           </div>
         )}
         {keyboardElement}
diff --git a/src/utils/is-node-with-content.ts b/src/utils/is-node-with-content.ts
index 62849cc73..168bd33ea 100644
--- a/src/utils/is-node-with-content.ts
+++ b/src/utils/is-node-with-content.ts
@@ -2,6 +2,9 @@ import type { ReactNode } from 'react'
 
 type Regulated<T> = T extends null | undefined | false ? never : T
 
+/**
+ * Check if the `node` is visible Node (not null, undefined, or false)
+ */
 export function isNodeWithContent(
   node: ReactNode
 ): node is Regulated<ReactNode> {
diff --git a/src/utils/with-default-props.tsx b/src/utils/with-default-props.tsx
index 3446d7dea..509f1014a 100644
--- a/src/utils/with-default-props.tsx
+++ b/src/utils/with-default-props.tsx
@@ -1,13 +1,33 @@
 export function mergeProps<A, B>(a: A, b: B): B & A
 export function mergeProps<A, B, C>(a: A, b: B, c: C): C & B & A
+export function mergeProps<A, B, C, D>(a: A, b: B, c: C, d: D): D & C & B & A
 export function mergeProps(...items: any[]) {
   const ret: any = {}
   items.forEach(item => {
-    Object.keys(item).forEach(key => {
-      if (item[key] !== undefined) {
-        ret[key] = item[key]
-      }
-    })
+    if (item) {
+      Object.keys(item).forEach(key => {
+        if (item[key] !== undefined) {
+          ret[key] = item[key]
+        }
+      })
+    }
   })
   return ret
 }
+
+/**
+ * Merge props and return the first non-undefined value.
+ * The later has higher priority. e.g. (10, 1, 5) => 5 wins.
+ * This is useful with legacy props that have been deprecated.
+ */
+export function mergeProp<T, DefaultT extends T = T>(
+  defaultProp: DefaultT,
+  ...propList: T[]
+): T | undefined {
+  for (let i = propList.length - 1; i >= 0; i -= 1) {
+    if (propList[i] !== undefined) {
+      return propList[i]
+    }
+  }
+  return defaultProp
+}


Test patch:
diff --git a/src/components/center-popup/tests/center-popup.test.tsx b/src/components/center-popup/tests/center-popup.test.tsx
new file mode 100644
index 000000000..72220957b
--- /dev/null
+++ b/src/components/center-popup/tests/center-popup.test.tsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import { render, screen, testA11y } from 'testing'
+import ConfigProvider from '../../config-provider'
+import { CenterPopup } from '../center-popup'
+
+describe('center-popup', () => {
+  test('a11y', async () => {
+    await testA11y(<CenterPopup visible>foobar</CenterPopup>)
+  })
+
+  describe('closeIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(
+        <CenterPopup visible showCloseButton>
+          foobar
+        </CenterPopup>
+      )
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(
+        <CenterPopup visible showCloseButton closeIcon='bamboo'>
+          foobar
+        </CenterPopup>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider popup={{ closeIcon: 'little' }}>
+          <CenterPopup visible showCloseButton>
+            foobar
+          </CenterPopup>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider popup={{ closeIcon: 'little' }}>
+          <CenterPopup visible showCloseButton closeIcon='bamboo'>
+            foobar
+          </CenterPopup>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
+})
diff --git a/src/components/check-list/tests/check-list.test.tsx b/src/components/check-list/tests/check-list.test.tsx
index bbdd9bb5e..7faaf0751 100644
--- a/src/components/check-list/tests/check-list.test.tsx
+++ b/src/components/check-list/tests/check-list.test.tsx
@@ -1,6 +1,7 @@
 import React from 'react'
-import { fireEvent, render, testA11y } from 'testing'
+import { fireEvent, render, screen, testA11y } from 'testing'
 import CheckList from '../'
+import ConfigProvider from '../../config-provider'
 
 const classPrefix = `adm-check-list`
 
@@ -74,4 +75,52 @@ describe('CheckList', () => {
     expect(getByText('YES')).toBeVisible()
     expect(getByText('NO')).toBeVisible()
   })
+
+  describe('closeIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(
+        <CheckList defaultValue={['B']}>
+          <CheckList.Item value='A'>A</CheckList.Item>
+          <CheckList.Item value='B'>B</CheckList.Item>
+        </CheckList>
+      )
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(
+        <CheckList defaultValue={['B']} activeIcon='bamboo'>
+          <CheckList.Item value='A'>A</CheckList.Item>
+          <CheckList.Item value='B'>B</CheckList.Item>
+        </CheckList>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider checkList={{ activeIcon: 'little' }}>
+          <CheckList defaultValue={['B']}>
+            <CheckList.Item value='A'>A</CheckList.Item>
+            <CheckList.Item value='B'>B</CheckList.Item>
+          </CheckList>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider checkList={{ activeIcon: 'little' }}>
+          <CheckList defaultValue={['B']} activeIcon='bamboo'>
+            <CheckList.Item value='A'>A</CheckList.Item>
+            <CheckList.Item value='B'>B</CheckList.Item>
+          </CheckList>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/collapse/tests/collapse.test.tsx b/src/components/collapse/tests/collapse.test.tsx
index 79321d614..5d5c9dad6 100644
--- a/src/components/collapse/tests/collapse.test.tsx
+++ b/src/components/collapse/tests/collapse.test.tsx
@@ -1,5 +1,5 @@
-import { render, screen, testA11y, fireEvent, waitFor } from 'testing'
 import * as React from 'react'
+import { fireEvent, render, screen, testA11y, waitFor } from 'testing'
 import Collapse from '../'
 
 const classPrefix = `adm-collapse`
@@ -100,3 +100,82 @@ test('disabled', async () => {
   expect(screen.queryByTestId('first')).toBeVisible()
   expect(screen.queryByTestId('second')).toBe(null)
 })
+
+describe('arrow', () => {
+  it('arrow (deprecated) of collapse', async () => {
+    render(
+      <Collapse arrow='foobar'>
+        <Collapse.Panel key='1' title='第一项'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('foobar')).toBeVisible()
+  })
+
+  it('arrow (deprecated) of panel', async () => {
+    render(
+      <Collapse>
+        <Collapse.Panel key='1' title='第一项' arrow='foobar'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('foobar')).toBeVisible()
+  })
+
+  it('arrow (deprecated) of both collapse and pannel', async () => {
+    render(
+      <Collapse arrow='foo'>
+        <Collapse.Panel key='1' title='第一项' arrow='bar'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('bar')).toBeVisible()
+  })
+
+  it('both arrow (deprecated) and arrowIcon of collapse', async () => {
+    render(
+      <Collapse arrow='foo' arrowIcon='bar'>
+        <Collapse.Panel key='1' title='第一项'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('bar')).toBeVisible()
+  })
+
+  it('both (deprecated) arrow and arrowIcon of pannel', async () => {
+    render(
+      <Collapse>
+        <Collapse.Panel key='1' title='第一项' arrow='foo' arrowIcon='bar'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('bar')).toBeVisible()
+  })
+
+  it('arrow (deprecated) of collapse and arrowIcon of panel', async () => {
+    render(
+      <Collapse arrow='foo'>
+        <Collapse.Panel key='1' title='第一项' arrowIcon='bar'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('bar')).toBeVisible()
+  })
+
+  it('arrowIcon of collapse and arrow (deprecated) of panel', async () => {
+    render(
+      <Collapse arrowIcon='foo'>
+        <Collapse.Panel key='1' title='第一项' arrow='bar'>
+          <div data-testid='first'>这里是第一项的内容</div>
+        </Collapse.Panel>
+      </Collapse>
+    )
+    expect(screen.getByText('bar')).toBeVisible()
+  })
+})
diff --git a/src/components/dropdown/tests/dropdown.test.tsx b/src/components/dropdown/tests/dropdown.test.tsx
index 852afc14a..5883482a7 100644
--- a/src/components/dropdown/tests/dropdown.test.tsx
+++ b/src/components/dropdown/tests/dropdown.test.tsx
@@ -107,4 +107,47 @@ describe('Dropdown', () => {
     fireEvent.click(screen.getByText('sorter'))
     expect(screen.getByText('click2'))
   })
+
+  describe('arrow', () => {
+    it('Dropdown - arrow', () => {
+      render(
+        <Dropdown arrow='little'>
+          <Dropdown.Item title='sorter' key='sorter' />
+        </Dropdown>
+      )
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('Dropdown - arrowIcon', () => {
+      render(
+        <Dropdown arrowIcon='bamboo' arrow='little'>
+          <Dropdown.Item title='sorter' key='sorter' />
+        </Dropdown>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('Dropdown.Item - arrow', () => {
+      render(
+        <Dropdown arrowIcon='ignore' arrow='ignore'>
+          <Dropdown.Item title='sorter' key='sorter' arrow='little' />
+        </Dropdown>
+      )
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('Dropdown.Item - arrowIcon', () => {
+      render(
+        <Dropdown arrowIcon='ignore' arrow='ignore'>
+          <Dropdown.Item
+            title='sorter'
+            key='sorter'
+            arrow='little'
+            arrowIcon='bamboo'
+          />
+        </Dropdown>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/form/tests/form.test.tsx b/src/components/form/tests/form.test.tsx
index 037cecd75..ffe40c498 100644
--- a/src/components/form/tests/form.test.tsx
+++ b/src/components/form/tests/form.test.tsx
@@ -1,9 +1,10 @@
 import React from 'react'
-import { fireEvent, render, waitFor } from 'testing'
+import { fireEvent, render, screen, waitFor } from 'testing'
 import Form from '..'
-import Input from '../../input'
 import Button from '../../button'
 import Checkbox from '../../checkbox'
+import ConfigProvider from '../../config-provider'
+import Input from '../../input'
 
 const classPrefix = `adm-form`
 
@@ -354,4 +355,121 @@ describe('Form', () => {
 
     jest.useRealTimers()
   })
+
+  describe('helpIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(
+        <Form>
+          <Form.Item name='name' label='name' help='hello world'>
+            <Input />
+          </Form.Item>
+        </Form>
+      )
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(
+        <Form>
+          <Form.Item
+            name='name'
+            label='name'
+            help='hello world'
+            helpIcon='bamboo'
+          >
+            <Input />
+          </Form.Item>
+        </Form>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider form={{ helpIcon: 'little' }}>
+          <Form>
+            <Form.Item name='name' label='name' help='hello world'>
+              <Input />
+            </Form.Item>
+          </Form>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider form={{ helpIcon: 'little' }}>
+          <Form>
+            <Form.Item
+              name='name'
+              label='name'
+              help='hello world'
+              helpIcon='bamboo'
+            >
+              <Input />
+            </Form.Item>
+          </Form>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
+
+  describe('arrow', () => {
+    it('legacy default', () => {
+      const { baseElement } = render(
+        <Form>
+          <Form.Item arrow />
+        </Form>
+      )
+      expect(
+        baseElement.querySelector('.adm-list-item-content-arrow')
+      ).toBeTruthy()
+    })
+
+    it('legacy custom', () => {
+      render(
+        <Form>
+          <Form.Item arrow='little' />
+        </Form>
+      )
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('arrowIcon', () => {
+      render(
+        <Form>
+          <Form.Item arrow='little' arrowIcon='bamboo' />
+        </Form>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider list={{ arrowIcon: 'little' }}>
+          <Form>
+            <Form.Item clickable />
+          </Form>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider list={{ arrowIcon: 'little' }}>
+          <Form>
+            <Form.Item clickable arrowIcon='bamboo' />
+          </Form>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/input/tests/input.test.tsx b/src/components/input/tests/input.test.tsx
index 46fee2229..7f7703098 100644
--- a/src/components/input/tests/input.test.tsx
+++ b/src/components/input/tests/input.test.tsx
@@ -1,6 +1,7 @@
 import React, { createRef } from 'react'
-import { render, fireEvent, act, userEvent, waitFor, screen } from 'testing'
+import { act, fireEvent, render, screen, userEvent, waitFor } from 'testing'
 import Input from '..'
+import ConfigProvider from '../../config-provider'
 import { InputRef } from '../input'
 
 jest.mock('../../../utils/validate', () => ({
@@ -208,4 +209,54 @@ describe('Input', () => {
 
     expect(input.value).toBe('012')
   })
+
+  describe('clearIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(<Input value='foobar' clearable />)
+
+      act(() => {
+        document.querySelector('input')?.focus()
+      })
+
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(<Input value='foobar' clearable clearIcon='bamboo' />)
+
+      act(() => {
+        document.querySelector('input')?.focus()
+      })
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider input={{ clearIcon: 'little' }}>
+          <Input value='foobar' clearable />
+        </ConfigProvider>
+      )
+
+      act(() => {
+        document.querySelector('input')?.focus()
+      })
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider input={{ clearIcon: 'little' }}>
+          <Input value='foobar' clearable clearIcon='bamboo' />
+        </ConfigProvider>
+      )
+
+      act(() => {
+        document.querySelector('input')?.focus()
+      })
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/list/tests/list.test.tsx b/src/components/list/tests/list.test.tsx
index 5a0d936cc..3e255ab45 100644
--- a/src/components/list/tests/list.test.tsx
+++ b/src/components/list/tests/list.test.tsx
@@ -1,6 +1,7 @@
 import React, { createRef } from 'react'
-import { render, testA11y } from 'testing'
+import { render, screen, testA11y } from 'testing'
 import List from '..'
+import ConfigProvider from '../../config-provider'
 import { ListRef } from '../list'
 
 describe('list', () => {
@@ -23,4 +24,59 @@ describe('list', () => {
     expect(ref.current?.nativeElement).toBeDefined()
     expect(baseElement).toMatchSnapshot()
   })
+
+  describe('arrow', () => {
+    it('show arrow', () => {
+      const { baseElement } = render(
+        <List>
+          <List.Item arrow />
+        </List>
+      )
+      expect(
+        baseElement.querySelector('.adm-list-item-content-arrow')
+      ).toBeTruthy()
+    })
+
+    it('legacy arrow', () => {
+      render(
+        <List>
+          <List.Item arrow='little' />
+        </List>
+      )
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('arrowIcon', () => {
+      render(
+        <List>
+          <List.Item arrow='little' arrowIcon='bamboo' />
+        </List>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider list={{ arrowIcon: 'little' }}>
+          <List>
+            <List.Item clickable />
+          </List>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider list={{ arrowIcon: 'little' }}>
+          <List>
+            <List.Item clickable arrowIcon='bamboo' />
+          </List>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/nav-bar/tests/nav-bar.test.tsx b/src/components/nav-bar/tests/nav-bar.test.tsx
new file mode 100644
index 000000000..dc2791733
--- /dev/null
+++ b/src/components/nav-bar/tests/nav-bar.test.tsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import { render, screen } from 'testing'
+import NavBar from '..'
+import ConfigProvider from '../../config-provider'
+
+describe('NavBar', () => {
+  test('render title', () => {
+    render(<NavBar>Title</NavBar>)
+    expect(screen.getByText('Title')).toBeInTheDocument()
+  })
+
+  describe('backIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(<NavBar backIcon>Title</NavBar>)
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('legacy', () => {
+      const { baseElement } = render(<NavBar backArrow>Title</NavBar>)
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(<NavBar backIcon='bamboo'>Title</NavBar>)
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('props override legacy props', () => {
+      render(
+        <NavBar backArrow='little' backIcon='bamboo'>
+          Title
+        </NavBar>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider navBar={{ backIcon: 'little' }}>
+          <NavBar backIcon>Title</NavBar>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider navBar={{ backIcon: 'little' }}>
+          <NavBar backIcon='bamboo'>Title</NavBar>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
+})
diff --git a/src/components/notice-bar/tests/notice-bar.test.tsx b/src/components/notice-bar/tests/notice-bar.test.tsx
index 66490b704..394d4fac2 100644
--- a/src/components/notice-bar/tests/notice-bar.test.tsx
+++ b/src/components/notice-bar/tests/notice-bar.test.tsx
@@ -1,6 +1,7 @@
 import React from 'react'
-import { render, testA11y, fireEvent, sleep } from 'testing'
+import { fireEvent, render, screen, sleep, testA11y } from 'testing'
 import NoticeBar from '..'
+import ConfigProvider from '../../config-provider'
 
 const classPrefix = `adm-notice-bar`
 
@@ -30,7 +31,7 @@ describe('NoticeBar', () => {
     )
 
     const el = getByTestId('notice')
-    const iconEl = el.querySelectorAll(`.${classPrefix}-close-icon`)[0]
+    const iconEl = el.querySelectorAll(`.${classPrefix}-close`)[0]
     expect(iconEl).toBeVisible()
 
     fireEvent.click(iconEl)
@@ -107,4 +108,36 @@ describe('NoticeBar', () => {
 
     expect(handleClick).toHaveBeenCalled()
   })
+
+  describe('closeIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(<NoticeBar content='foobar' closeable />)
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(<NoticeBar content='foobar' closeable closeIcon='bamboo' />)
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider noticeBar={{ closeIcon: 'little' }}>
+          <NoticeBar content='foobar' closeable />
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider noticeBar={{ closeIcon: 'little' }}>
+          <NoticeBar content='foobar' closeable closeIcon='bamboo' />
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/popup/tests/popup.test.tsx b/src/components/popup/tests/popup.test.tsx
index 6565beb1f..33b951eac 100644
--- a/src/components/popup/tests/popup.test.tsx
+++ b/src/components/popup/tests/popup.test.tsx
@@ -1,8 +1,13 @@
 import * as React from 'react'
-import { render, mockDrag, act, waitFor } from 'testing'
+import { mockDrag, render, screen, testA11y } from 'testing'
 import Popup from '..'
+import ConfigProvider from '../../config-provider'
 
 describe('Popup', () => {
+  test('a11y', async () => {
+    await testA11y(<Popup visible>foobar</Popup>)
+  })
+
   test('top swipe should be closed', async () => {
     const onClose = jest.fn()
     render(
@@ -42,4 +47,48 @@ describe('Popup', () => {
     )
     expect(onClose).toBeCalledTimes(1)
   })
+
+  describe('closeIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(
+        <Popup visible showCloseButton>
+          foobar
+        </Popup>
+      )
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('props', () => {
+      render(
+        <Popup visible showCloseButton closeIcon='bamboo'>
+          foobar
+        </Popup>
+      )
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider popup={{ closeIcon: 'little' }}>
+          <Popup visible showCloseButton>
+            foobar
+          </Popup>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider popup={{ closeIcon: 'little' }}>
+          <Popup visible showCloseButton closeIcon='bamboo'>
+            foobar
+          </Popup>
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/result/tests/use-result-icon.test.tsx b/src/components/result/tests/use-result-icon.test.tsx
new file mode 100644
index 000000000..2b4b5fde6
--- /dev/null
+++ b/src/components/result/tests/use-result-icon.test.tsx
@@ -0,0 +1,44 @@
+import React from 'react'
+import { render, screen } from 'testing'
+import ConfigProvider from '../../config-provider'
+import { useResultIcon } from '../use-result-icon'
+
+const ResultIcon = (props: any) => {
+  const icon = useResultIcon(props.status)
+  return <div>{icon}</div>
+}
+
+describe('useResultIcon', () => {
+  test('renders with success status', () => {
+    render(<ResultIcon status='success' />)
+    expect(document.querySelector('.antd-mobile-icon')).toBeTruthy()
+  })
+  test('renders with error status', () => {
+    render(<ResultIcon status='error' />)
+    expect(document.querySelector('.antd-mobile-icon')).toBeTruthy()
+  })
+  test('renders with info status', () => {
+    render(<ResultIcon status='info' />)
+    expect(document.querySelector('.antd-mobile-icon')).toBeTruthy()
+  })
+  test('renders with waiting status', () => {
+    render(<ResultIcon status='waiting' />)
+    expect(document.querySelector('.antd-mobile-icon')).toBeTruthy()
+  })
+  test('renders with warning status', () => {
+    render(<ResultIcon status='warning' />)
+    expect(document.querySelector('.antd-mobile-icon')).toBeTruthy()
+  })
+  test('renders with none status', () => {
+    render(<ResultIcon />)
+    expect(document.querySelector('.antd-mobile-icon')).toBeFalsy()
+  })
+  test('renders with context', () => {
+    render(
+      <ConfigProvider result={{ successIcon: 'foobar' }}>
+        <ResultIcon status='success' />
+      </ConfigProvider>
+    )
+    expect(screen.getByText('foobar')).toBeTruthy()
+  })
+})
diff --git a/src/components/search-bar/tests/search-bar.test.tsx b/src/components/search-bar/tests/search-bar.test.tsx
index 53543ed29..39beb6346 100644
--- a/src/components/search-bar/tests/search-bar.test.tsx
+++ b/src/components/search-bar/tests/search-bar.test.tsx
@@ -1,14 +1,15 @@
 import React, { createRef } from 'react'
 import {
-  render,
-  testA11y,
+  act,
   fireEvent,
+  render,
   screen,
-  waitForElementToBeRemoved,
+  testA11y,
   userEvent,
-  act,
+  waitForElementToBeRemoved,
 } from 'testing'
 import SearchBar, { SearchBarRef } from '..'
+import ConfigProvider from '../../config-provider'
 
 const classPrefix = `adm-search-bar`
 
@@ -99,4 +100,46 @@ describe('adm-search-bar', () => {
     expect(input).not.toHaveFocus()
     expect(onBlur).toBeCalled()
   })
+
+  describe('searchIcon', () => {
+    it('default', () => {
+      const { baseElement } = render(<SearchBar />)
+      expect(baseElement.querySelector('.antd-mobile-icon')).toBeTruthy()
+    })
+
+    it('legacy', () => {
+      render(<SearchBar icon='little' />)
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props', () => {
+      render(<SearchBar searchIcon='bamboo' />)
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('props override legacy props', () => {
+      render(<SearchBar icon='little' searchIcon='bamboo' />)
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+
+    it('context', () => {
+      render(
+        <ConfigProvider searchBar={{ searchIcon: 'little' }}>
+          <SearchBar />
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('little')).toBeVisible()
+    })
+
+    it('props override context', () => {
+      render(
+        <ConfigProvider searchBar={{ searchIcon: 'little' }}>
+          <SearchBar searchIcon='bamboo' />
+        </ConfigProvider>
+      )
+
+      expect(screen.getByText('bamboo')).toBeVisible()
+    })
+  })
 })
diff --git a/src/components/toast/tests/toast.test.tsx b/src/components/toast/tests/toast.test.tsx
index 44cacfc5f..0d17950bb 100644
--- a/src/components/toast/tests/toast.test.tsx
+++ b/src/components/toast/tests/toast.test.tsx
@@ -60,20 +60,18 @@ describe('Toast', () => {
     fireEvent.click(getByText('success'))
     await waitForContentShow('content success')
     expect(
-      document.querySelectorAll(`.${classPrefix}-icon-success`)[0]
+      document.getElementById('CheckOutline-CheckOutline')
     ).toBeInTheDocument()
 
     fireEvent.click(getByText('fail'))
     await waitForContentShow('content fail')
     expect(
-      document.querySelectorAll(`.${classPrefix}-icon-fail`)[0]
+      document.getElementById('CloseOutline-CloseOutline')
     ).toBeInTheDocument()
 
     fireEvent.click(getByText('loading'))
     await waitForContentShow('content loading')
-    expect(
-      document.querySelectorAll(`.${classPrefix}-loading`)[0]
-    ).toBeInTheDocument()
+    expect(document.querySelector('.adm-spin-loading')).toBeInTheDocument()
   })
 
   test('custom icon', async () => {