Modal

Modalは、メインコンテンツの上にオーバーレイ表示されるダイアログコンポーネントです。通常のModalと、ユーザーの確認が必要なAlertModalの2タイプを提供します。

2
Types
6
Sizes
2
Scroll Mode
Radix
Base

Playground

Preview
SMModal
Modal
Type
Size
Scroll
Content
Options
Button Style
Cancel
Confirm
const [open, setOpen] = useState(false)

<Modal open={open} onOpenChange={setOpen}>
  <Modal.Trigger asChild>
    <Button variant="outline">Open Modal</Button>
  </Modal.Trigger>
  <Modal.Content>
    <Modal.Header>
      <Modal.Title>Modal Title</Modal.Title>
      <Modal.Description>This is a description of the modal.</Modal.Description>
    </Modal.Header>
    <Modal.Body>
      <p>This is a modal dialog. You can place any content here, including forms, confirmations, or informational messages.</p>
    </Modal.Body>
    <Modal.Footer>
      <Modal.Close asChild>
        <Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
      </Modal.Close>
      <Button size="md" pressEffect={false}>Confirm</Button>
    </Modal.Footer>
  </Modal.Content>
</Modal>

Sizes

xs簡単な確認・アラート
max-width360px
sm (default)標準(デフォルト)
max-width480px
md標準フォーム
max-width600px
lg複雑なフォーム
max-width760px
xlテーブル・ダッシュボード
max-width960px
fullフル幅
max-widthFull
パディング(全サイズ共通)
Header24px · 下 0
Body24px
Footer24px · 上 0
Closeright 16px, top 16px

Features

Scroll Behavior

"outside": モーダル全体スクロール。"inside": Bodyのみスクロール、Header/Footer固定。

{/* Outside scroll — entire modal scrolls within viewport */}
<Modal.Content scrollBehavior="outside">
  <Modal.Header>
    <Modal.Title>Outside Scroll</Modal.Title>
    <Modal.Description>The entire modal scrolls within the viewport.</Modal.Description>
  </Modal.Header>
  <Modal.Body>
    <p>Long content here...</p>
  </Modal.Body>
</Modal.Content>

{/* Inside scroll — only body scrolls; header stays fixed */}
<Modal.Content scrollBehavior="inside">
  <Modal.Header>
    <Modal.Title>Inside Scroll</Modal.Title>
    <Modal.Description>Only the body scrolls; header and footer stay fixed.</Modal.Description>
  </Modal.Header>
  <Modal.Body>
    <p>Long content here...</p>
  </Modal.Body>
</Modal.Content>

Sidebar Layout

lg / xl / full サイズ限定。Modal.Body を p-0 flex で分割し、左ナビ+右コンテンツのレイアウトを構成できます。

const [active, setActive] = useState(0)
const navItems = ['General', 'Security', 'Notifications']

<Modal.Content size="lg" scrollBehavior="inside" showCloseButton={false}>
  <Modal.Body className="p-0 flex min-h-0">
    {/* Left sidebar */}
    <div className="w-48 shrink-0 flex flex-col">
      <div className="p-3">
        <Modal.Close asChild>
          <Button variant="ghost" size="md" pressEffect={false} className="w-9 h-9 p-0">
            <X className="icon-sm" />
            <span className="sr-only">Close</span>
          </Button>
        </Modal.Close>
      </div>
      <nav className="flex-1 px-2 pb-4 space-y-1">
        {navItems.map((item, i) => (
          <button key={item} onClick={() => setActive(i)}
            className={cn("w-full text-left px-3 py-2 text-md rounded-lg transition-colors",
              active === i
                ? "bg-background-muted text-foreground font-semibold"
                : "text-text-muted hover:text-foreground hover:bg-background-muted"
            )}>
            {item}
          </button>
        ))}
      </nav>
    </div>
    <Divider orientation="vertical" color="muted" className="mx-0 h-auto" />
    {/* Right content */}
    <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
      <div className="px-6 pt-6 pb-4 shrink-0">
        <h2 className="text-lg font-semibold">{navItems[active]}</h2>
      </div>
      <div className="flex-1 px-6 pb-6 overflow-y-auto">
        <p className="text-md text-text-muted">Content area...</p>
      </div>
    </div>
  </Modal.Body>
</Modal.Content>

Close Button

falseで閉じるボタン非表示。closeIcon propでアイコン差し替え可能。

{/* With close button (default) — X only */}
<Modal.Content showCloseButton>
  <Modal.Header>...</Modal.Header>
  <Modal.Body>...</Modal.Body>
</Modal.Content>

{/* Without close button — footer actions provide exit */}
<Modal.Content showCloseButton={false}>
  <Modal.Header>...</Modal.Header>
  <Modal.Body>...</Modal.Body>
  <Modal.Footer>
    <Modal.Close asChild>
      <Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
    </Modal.Close>
    <Button size="md" pressEffect={false}>Confirm</Button>
  </Modal.Footer>
</Modal.Content>

API

Component Structure

ModalRadix Dialog
.Trigger.ContentProps.Header.Title.Description.Body.Footer.CloseProps.Overlay.Portal
AlertModalRadix AlertDialog
.Trigger.ContentProps.Header.Title.Description.Body.Footer.Action.Cancel.Overlay.Portal

Props

Modal.Content

size"sm"
"xs" | "sm" | "md" | "lg" | "xl" | "full"

コンテンツの最大幅 (360/480/600/760/960/full)

scrollBehavior"outside"
"inside" | "outside"

オーバーフロー時のスクロール位置

showCloseButtontrue
boolean

内蔵の閉じるボタンを表示

closeIconBuilt-in SVG icon
ReactNode

カスタム閉じるアイコン

AlertModal.Content

size"sm"
"xs" | "sm"

コンテンツの最大幅 (360/480)

Modal.Close

asChildfalse
boolean

子要素をトリガーとして使用

Anatomy

1
2
3
4
Modal title
5
Modal description
6
7
8
1
Overlay
背景オーバーレイ
2
Content
ダイアログ本体
3
Header
タイトル領域
4
Title
見出しテキスト
5
Description
補足説明テキスト
6
Close
閉じるボタン
7
Body
メインコンテンツ
8
Footer
アクションボタン

Best Practices

推奨

  • 破壊的操作にはAlertModalを使用する
  • モーダルのタイトルと説明を必ず設定する
  • 長いコンテンツにはscrollBehavior="inside"を使用する
  • コンテンツに適したサイズを選択する

避けるべき

  • モーダルの中にモーダルをネストしない
  • 簡単な通知にモーダルを使わない(Toastを推奨)
  • 閉じる手段なしのモーダルを作らない
  • 通常の操作にAlertModalを使わない(確認が必要な場面のみ)

Accessibility

キーボード操作

Escapeキーでモーダルを閉じることができます。AlertModalではオーバーレイクリックでは閉じません。モーダル内ではTabキーでフォーカスがトラップされ、モーダル外の要素にフォーカスが移動しません。

ARIA / WCAG

  • role="dialog" Radix Dialogが自動設定
  • role="alertdialog" AlertModalが自動設定
  • aria-labelledby Modal.Titleへ自動リンク
  • aria-describedby Modal.Descriptionへ自動リンク
  • フォーカストラップ(モーダル内にフォーカスを制限)
  • 閉じるボタンにsr-onlyラベル設定