Skip to documentation
UI

ConfigProvider

Configures library-wide CDN resource resolution and the motion runtime for descendant components.

import { ConfigProvider } from '@lobehub/ui';
Theme

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.

Loading preview

Custom CDN

At the same time, it provides custom CDN configuration, set proxy to custom and configure through the customCdnFn attribute.

Loading preview

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:

Loading preview

If your app uses LazyMotion:

Loading preview

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:

Loading preview

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

ConfigProvider properties
childrenRequired
ReactNode
config
Config
locale
string
motionRequired
MotionComponentType
resources
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>>[] | 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

PropertyDescriptionTypeDefault
proxyCDN proxy to use for external resources'jsdelivr' | 'unpkg' | 'aliyun' | 'custom'-
customCdnFnCustom function for generating CDN URLs when proxy is 'custom'CdnFn-
aAsCustom component to use for rendering anchor tagsElementType-
imgAsCustom component to use for rendering image tagsElementType-
globalFocusRingInstall the document-wide keyboard focus ringbooleantrue
imgUnoptimizedWhether to disable image optimizationboolean-

CdnFn

type CdnFn = ({ pkg, version, path }: CdnApi) => string;

interface CdnApi {
  pkg: string;
  version: string;
  path: string;
}