ConfigProvider
Configures library-wide CDN resource resolution and the motion runtime for descendant components.
import { ConfigProvider } from '@lobehub/ui';ConfigProvider is a context provider component that allows you to configure global settings for other components in the library, such as CDN settings for external resources and the motion runtime.
Default
The proxy provides two CDN resolution addresses, aliyun and unpkg, with aliyun as the default. This can be configured through the cdn attribute of ConfigProvider.
Custom CDN
At the same time, it provides custom CDN configuration, set proxy to custom and configure through the customCdnFn attribute.
Provider order
ConfigProvider must wrap ThemeProvider, never the other way round. ThemeProvider renders the antd App that hosts the static notification / modal holders, and it reads the CDN config for its webfonts — both need the contexts from ConfigProvider above them. Components never provide these contexts themselves; anything rendered inside a ConfigProvider (including through antd's static APIs) receives them.
<ConfigProvider motion={motion}>
<ThemeProvider>
<App />
</ThemeProvider>
</ConfigProvider>Motion
Lobe UI uses Motion components via context. You must pass a motion component to ConfigProvider. If your app uses LazyMotion, pass m:
If your app uses LazyMotion:
Global keyboard focus
ConfigProvider installs one global focus ring per document, including native controls and controls outside the provider's React subtree. No component focus class or registration is required. Set config={{ globalFocusRing: false }} on all providers to disable installation.
Use Tab to walk through the labeled baseline, clipping, window lifecycle and local-fallback cases:
Without ConfigProvider, install it directly and call the returned cleanup when your application unmounts:
import { installGlobalFocusRing } from '@lobehub/ui';
const cleanup = installGlobalFocusRing();The decorative top-layer ring uses CSS anchors to follow scrolling, resizing and translation without a continuous JavaScript measurement loop. It preserves :focus-visible, reduced-motion and forced-colors preferences. Only successfully covered controls lose their local outline; existing Base UI rings remain the fallback.
Text inputs, textareas and contenteditable regions retain their existing focus styles. To opt out another control or an entire region, add data-lobe-focus-ring="off" to the element or an ancestor. This only skips the global ring; keep an appropriate local focus indication for keyboard users.
Unsupported browsers, fragmented multiline links and shadow-root controls retain their original focus treatment. Install separately in each iframe document. Rotated shapes are not reproduced; the ring follows a rectangular bounding box. Partially clipped controls receive a full ring; fully clipped anchors are hidden by the browser.
APIs
ConfigProvider
childrenRequiredReactNodeconfigConfiglocalestringmotionRequiredMotionComponentTyperesourcesPartial<Record<'chat.avatar' | 'chat.placeholder' | 'tokenTag.overload' | 'tokenTag.remained' | 'tokenTag.used' | 'common.cancel' | 'common.confirm' | 'common.delete' | 'common.edit' | 'common.empty' | 'editableMessage.addProps' | 'editableMessage.delete' | 'editableMessage.input' | 'editableMessage.inputPlaceholder' | 'editableMessage.output' | 'editableMessage.outputPlaceholder' | 'editableMessage.system' | 'emojiPicker.delete' | 'emojiPicker.draggerDesc' | 'emojiPicker.emoji' | 'emojiPicker.fileTypeError' | 'emojiPicker.upload' | 'emojiPicker.uploadBtn' | 'form.reset' | 'form.submit' | 'form.unsavedChanges' | 'form.unsavedWarning' | 'hotkey.clear' | 'hotkey.conflict' | 'hotkey.invalidCombination' | 'hotkey.placeholder' | 'hotkey.reset' | 'image.actualSize' | 'image.close' | 'image.copy' | 'image.copyFailed' | 'image.copySuccess' | 'image.download' | 'image.downloadFailed' | 'image.downloadSuccess' | 'image.fitToScreen' | 'image.flipHorizontal' | 'image.flipVertical' | 'image.more' | 'image.next' | 'image.prev' | 'image.rotateLeft' | 'image.rotateRight' | 'image.zoomIn' | 'image.zoomOut' | 'messageModal.cancel' | 'messageModal.confirm' | 'messageModal.edit' | 'sideNav.collapse' | 'sideNav.demoActiveLabel' | 'sideNav.demoFeatureAutoCollapseDesc' | 'sideNav.demoFeatureAutoCollapseTitle' | 'sideNav.demoFeaturePerformanceDesc' | 'sideNav.demoFeaturePerformanceTitle' | 'sideNav.demoFeatureResizeDesc' | 'sideNav.demoFeatureResizeTitle' | 'sideNav.demoFeatureSmartHandleDesc' | 'sideNav.demoFeatureSmartHandleTitle' | 'sideNav.demoFeaturesTitle' | 'sideNav.demoHint' | 'sideNav.demoSubtitle' | 'sideNav.demoTitle' | 'sideNav.expand', string>>[] | Record<string, Partial<Record<'chat.avatar' | 'chat.placeholder' | 'tokenTag.overload' | 'tokenTag.remained' | 'tokenTag.used' | 'common.cancel' | 'common.confirm' | 'common.delete' | 'common.edit' | 'common.empty' | 'editableMessage.addProps' | 'editableMessage.delete' | 'editableMessage.input' | 'editableMessage.inputPlaceholder' | 'editableMessage.output' | 'editableMessage.outputPlaceholder' | 'editableMessage.system' | 'emojiPicker.delete' | 'emojiPicker.draggerDesc' | 'emojiPicker.emoji' | 'emojiPicker.fileTypeError' | 'emojiPicker.upload' | 'emojiPicker.uploadBtn' | 'form.reset' | 'form.submit' | 'form.unsavedChanges' | 'form.unsavedWarning' | 'hotkey.clear' | 'hotkey.conflict' | 'hotkey.invalidCombination' | 'hotkey.placeholder' | 'hotkey.reset' | 'image.actualSize' | 'image.close' | 'image.copy' | 'image.copyFailed' | 'image.copySuccess' | 'image.download' | 'image.downloadFailed' | 'image.downloadSuccess' | 'image.fitToScreen' | 'image.flipHorizontal' | 'image.flipVertical' | 'image.more' | 'image.next' | 'image.prev' | 'image.rotateLeft' | 'image.rotateRight' | 'image.zoomIn' | 'image.zoomOut' | 'messageModal.cancel' | 'messageModal.confirm' | 'messageModal.edit' | 'sideNav.collapse' | 'sideNav.demoActiveLabel' | 'sideNav.demoFeatureAutoCollapseDesc' | 'sideNav.demoFeatureAutoCollapseTitle' | 'sideNav.demoFeaturePerformanceDesc' | 'sideNav.demoFeaturePerformanceTitle' | 'sideNav.demoFeatureResizeDesc' | 'sideNav.demoFeatureResizeTitle' | 'sideNav.demoFeatureSmartHandleDesc' | 'sideNav.demoFeatureSmartHandleTitle' | 'sideNav.demoFeaturesTitle' | 'sideNav.demoHint' | 'sideNav.demoSubtitle' | 'sideNav.demoTitle' | 'sideNav.expand', string>>> | Promise<TranslationResourcesMap>
Config
| Property | Description | Type | Default |
|---|---|---|---|
| proxy | CDN proxy to use for external resources | 'jsdelivr' | 'unpkg' | 'aliyun' | 'custom' | - |
| customCdnFn | Custom function for generating CDN URLs when proxy is 'custom' | CdnFn | - |
| aAs | Custom component to use for rendering anchor tags | ElementType | - |
| imgAs | Custom component to use for rendering image tags | ElementType | - |
| globalFocusRing | Install the document-wide keyboard focus ring | boolean | true |
| imgUnoptimized | Whether to disable image optimization | boolean | - |
CdnFn
type CdnFn = ({ pkg, version, path }: CdnApi) => string;
interface CdnApi {
pkg: string;
version: string;
path: string;
}