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}>
  <ModalTrigger asChild>
    <Button variant="outline">Open Modal</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Modal Title</ModalTitle>
      <ModalDescription>This is a description of the modal.</ModalDescription>
    </ModalHeader>
    <ModalBody>
      <p>This is a modal dialog. You can place any content here, including forms, confirmations, or informational messages.</p>
    </ModalBody>
    <ModalFooter>
      <ModalClose asChild>
        <Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
      </ModalClose>
      <Button size="md" pressEffect={false}>Confirm</Button>
    </ModalFooter>
  </ModalContent>
</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 */}
<ModalContent scrollBehavior="outside">
  <ModalHeader>
    <ModalTitle>Outside Scroll</ModalTitle>
    <ModalDescription>The entire modal scrolls within the viewport.</ModalDescription>
  </ModalHeader>
  <ModalBody>
    <p>Long content here...</p>
  </ModalBody>
</ModalContent>

{/* Inside scroll — only body scrolls; header stays fixed */}
<ModalContent scrollBehavior="inside">
  <ModalHeader>
    <ModalTitle>Inside Scroll</ModalTitle>
    <ModalDescription>Only the body scrolls; header and footer stay fixed.</ModalDescription>
  </ModalHeader>
  <ModalBody>
    <p>Long content here...</p>
  </ModalBody>
</ModalContent>

Sidebar Layout

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

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

<ModalContent size="lg" scrollBehavior="inside" showCloseButton={false}>
  <ModalBody className="p-0 flex min-h-0">
    {/* Left sidebar */}
    <div className="w-48 shrink-0 flex flex-col">
      <div className="p-3">
        <ModalClose 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>
        </ModalClose>
      </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">
        <ModalTitle>{navItems[active]}</ModalTitle>
      </div>
      <div className="flex-1 px-6 pb-6 overflow-y-auto">
        <p className="text-md text-text-muted">Content area...</p>
      </div>
    </div>
  </ModalBody>
</ModalContent>

Close Button

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

{/* With close button (default) — X only */}
<ModalContent showCloseButton>
  <ModalHeader>
    <ModalTitle>Title</ModalTitle>
  </ModalHeader>
  <ModalBody>...</ModalBody>
</ModalContent>

{/* Without close button — footer actions provide exit */}
<ModalContent showCloseButton={false}>
  <ModalHeader>
    <ModalTitle>Title</ModalTitle>
  </ModalHeader>
  <ModalBody>...</ModalBody>
  <ModalFooter>
    <ModalClose asChild>
      <Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
    </ModalClose>
    <Button size="md" pressEffect={false}>Confirm</Button>
  </ModalFooter>
</ModalContent>

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

ModalContent

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

カスタム閉じるアイコン

AlertModalContent

size"sm"
"xs" | "sm"

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

ModalClose

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

キーボード操作

Escモーダルを閉じる
Tabモーダル内フォーカス移動

ARIA / WCAG

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