PluginBench
Skill
Pass
Audit score 90

modal

lobehub/lobe-chat

LobeHub imperative modal conventions for createModal, confirmModal, and useModalContext patterns.

What is modal?

Standardized approach for building modals, dialogs, and confirm flows in LobeHub using imperative APIs. Use this when creating or migrating modal implementations to follow LobeHub's base-ui modal stack conventions.

  • Provides imperative modal creation via createModal and confirmModal functions
  • Enables modal content components to access close and dismiss controls via useModalContext
  • Establishes file structure and patterns for modal features (content component + factory function)
  • Supports both modern base-ui modals (@lobehub/ui/base-ui) and legacy antd-based modals (@lobehub/ui)
  • Integrates with i18n for modal titles and content translations
  • Offers semantic styling options (backdrop, popup, header, content) instead of antd-specific style keys

How to install modal

npx skills add https://github.com/lobehub/lobe-chat --skill modal
Prerequisites
  • @lobehub/ui/base-ui package installed (for recommended approach)
  • ModalHost component mounted near app root (required for createModal to render)
  • react-i18next for translations in modal content
Claude Code
Cursor
Windsurf
Cline

How to use modal

  1. 1.Create a content component (MyFeatureContent.tsx) that uses useModalContext to access close()
  2. 2.Create a factory function (index.tsx) that calls createModal with title, content, width, and other options
  3. 3.Import and call the factory function (e.g., createMyFeatureModal()) from event handlers
  4. 4.Use useTranslation in content components and import { t } from 'i18next' in factory functions for i18n
  5. 5.Ensure ModalHost from @lobehub/ui/base-ui is mounted at app root if not already present

Use cases

Good for
  • Creating a new settings or configuration modal with custom form content
  • Migrating existing declarative modals (with open state) to imperative createModal calls
  • Building confirmation dialogs with async onOk handlers
  • Implementing feature-specific modals that need to close themselves via useModalContext
  • Styling modals with consistent width, mask behavior, and footer options across the app
Who it's for
  • Frontend developers building features in LobeHub/lobe-chat
  • Teams migrating from declarative to imperative modal patterns
  • Developers maintaining modal consistency across a large codebase

modal FAQ

Should I use imperative or declarative modals?

Imperative (createModal) is recommended. It eliminates local open/close state management and simplifies component logic compared to declarative <Modal open={state} /> patterns.

What's the difference between @lobehub/ui and @lobehub/ui/base-ui modals?

@lobehub/ui/base-ui is the modern headless approach (preferred for new code); @lobehub/ui uses antd Modal props and is considered legacy. Migrate new work to base-ui.

Why isn't my createModal appearing on screen?

Ensure ModalHost from @lobehub/ui/base-ui is mounted once near your app root. Without it, createModal calls will not render.

How do I translate modal titles and content?

Use useTranslation in content components; in factory functions where hooks unavailable, import { t } from 'i18next' and call t('key', { ns: 'namespace' }).

How do I let users dismiss a modal by clicking outside?

Set maskClosable: true in createModal options. You can also control this dynamically via setCanDismissByClickOutside from useModalContext.

Full instructions (SKILL.md)

Source of truth, from lobehub/lobe-chat.


name: modal description: 'LobeHub imperative modal conventions. Use when creating or migrating modals, dialogs, popups, confirm flows, ModalHost wiring, createModal, confirmModal, useModalContext, or base-ui modal APIs.' user-invocable: false

Modal Imperative API Guide

Recommended: @lobehub/ui/base-ui

New code should use the base-ui modal stack (headless primitives, not antd Modal):

  • createModal, confirmModal, ModalHost from @lobehub/ui/base-ui
  • useModalContext from @lobehub/ui/base-ui inside modal content

Body slot: pass content (or children; runtime uses content ?? children).

Global ModalHost (required)

Base-ui createModal renders through a separate host from the root package. The app must mount ModalHost from @lobehub/ui/base-ui once near the root (e.g. next to other global hosts). Without it, createModal calls will not appear.

If the project only mounts ModalHost from @lobehub/ui, add a second lazy ModalHost from @lobehub/ui/base-ui until all imperative modals are migrated.

Why imperative?

ModeCharacteristicsRecommended
Declarativeopen state + <Modal />❌
ImperativeCall createModal(), no local state✅

File structure

features/
└── MyFeatureModal/
    ├── index.tsx            # export createXxxModal
    └── MyFeatureContent.tsx # modal body

1. Content (MyFeatureContent.tsx)

'use client';

import { useModalContext } from '@lobehub/ui/base-ui';
import { useTranslation } from 'react-i18next';

export const MyFeatureContent = () => {
  const { t } = useTranslation('namespace');
  const { close } = useModalContext();

  return <div>{/* ... */}</div>;
};

2. createModal (index.tsx)

'use client';

import { createModal } from '@lobehub/ui/base-ui';
import { t } from 'i18next';

import { MyFeatureContent } from './MyFeatureContent';

export const createMyFeatureModal = () =>
  createModal({
    content: <MyFeatureContent />,
    footer: null,
    maskClosable: true,
    styles: {
      content: { overflow: 'hidden', padding: 0 },
    },
    title: t('myFeature.title', { ns: 'setting' }),
    width: 'min(80%, 800px)',
  });

3. Usage

import { createMyFeatureModal } from '@/features/MyFeatureModal';

const handleOpen = useCallback(() => {
  createMyFeatureModal();
}, []);

return <Button onClick={handleOpen}>Open</Button>;

i18n

  • Content: useTranslation in components.
  • createModal options: import { t } from 'i18next' where hooks are unavailable.

useModalContext

const { close, setCanDismissByClickOutside } = useModalContext();

Common options (base-ui)

ImperativeModalProps builds on BaseModalProps: title, width, maskClosable, open, onOpenChange, footer, styles / classNames (keys: backdrop, popup, header, title, close, content, …).

PropertyNotes
contentMain body (preferred name vs children)
maskClosableClick outside to dismiss
styles.*Semantic regions, not antd styles.body

Confirm

import { confirmModal } from '@lobehub/ui/base-ui';

confirmModal({
  title: '…',
  content: '…',
  okText: '…',
  cancelText: '…',
  onOk: async () => {},
});

Legacy: @lobehub/ui (root)

Older call sites use createModal from @lobehub/ui, which is typed as antd Modal props (children, allowFullscreen, getContainer, destroyOnHidden, styles.body, etc.). Prefer migrating new work to @lobehub/ui/base-ui.

Examples (legacy): src/features/SkillStore/index.tsx, src/features/LibraryModal/CreateNew/index.tsx.


Examples

  • Base-ui (preferred): follow sections above; ensure base-ui ModalHost is mounted.
  • Legacy: src/features/SkillStore/index.tsx, src/features/LibraryModal/CreateNew/index.tsx