Glin UI
Primitive / Molecule

Modal / Dialog

Layered dialog for focused tasks or confirmations.

Edit on GitHub

Installation

Install from
pnpm dlx @glinui/cli@latest add modal

Prefer the package?

Usage

Overlay demos on this page render inside the preview stage so they stay contained. In your app they portal to the document body as usual.

Sizes

Choose sm, md or lg to match the amount of content.

"use client"
import {
Button,
Input,
Modal,
ModalClose,
ModalContent,
ModalDescription,
ModalFooter,
ModalHeader,
ModalTitle,
ModalTrigger
} from "@glinui/ui"
export function ModalSizesExample() {
return (
<div className="mx-auto flex flex-wrap items-center justify-center gap-3">
{(["sm", "md", "lg"] as const).map((size) => (
<Modal key={size}>
<ModalTrigger asChild><Button variant="outline" size="sm">{`Size ${size}`}</Button></ModalTrigger>
<ModalContent size={size}>
<ModalHeader>
<ModalTitle>Rename workspace</ModalTitle>
<ModalDescription>Pick a name your team will recognise.</ModalDescription>
</ModalHeader>
<Input aria-label="Workspace name" defaultValue="Lumen" />
<ModalFooter>
<ModalClose asChild><Button variant="ghost">Cancel</Button></ModalClose>
<ModalClose asChild><Button>Save name</Button></ModalClose>
</ModalFooter>
</ModalContent>
</Modal>
))}
</div>
)
}

Variants

Every look of the shared vocabulary: glinr (the default), plain (shadcn), solid, soft, outline, ghost, gradient and glass. Omit `variant` to follow the ambient design style.

import { Button, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, ModalTrigger } from "@glinui/ui"
const variants = ["glinr", "plain", "solid", "soft", "outline", "ghost", "gradient", "glass"] as const
export function Demo() {
return (
<div className="flex flex-wrap gap-2">
{variants.map((variant) => (
<Modal key={variant}>
<ModalTrigger asChild><Button size="sm" variant="outline">{variant}</Button></ModalTrigger>
<ModalContent variant={variant} size="sm">
<ModalHeader>
<ModalTitle>{variant} modal</ModalTitle>
<ModalDescription>Same content, different surface.</ModalDescription>
</ModalHeader>
<ModalFooter>
<ModalClose asChild><Button variant="ghost">Close</Button></ModalClose>
<Button>Continue</Button>
</ModalFooter>
</ModalContent>
</Modal>
))}
</div>
)
}

Glass (opt-in)

variant="glass" is opt-in. It blurs and tints whatever sits behind the panel, so it needs a colourful or photographic backdrop: switch the stage backdrop to see it.

import { Button, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, ModalTrigger } from "@glinui/ui"
export function Demo() {
return (
<Modal>
<ModalTrigger asChild><Button variant="outline">Open glass modal</Button></ModalTrigger>
<ModalContent variant="glass">
<ModalHeader>
<ModalTitle>Share workspace</ModalTitle>
<ModalDescription>The page behind stays visible through the panel.</ModalDescription>
</ModalHeader>
<ModalFooter>
<ModalClose asChild><Button variant="ghost">Cancel</Button></ModalClose>
<Button>Share</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

With Form Content

Place form fields inside the body area between `ModalHeader` and `ModalFooter`.

import { Button, Modal, ModalTrigger, ModalClose, ModalContent, ModalHeader, ModalFooter, ModalTitle, ModalDescription } from "@glinui/ui"

export function ModalFormDemo() {
  return (
    <Modal>
      <ModalTrigger asChild>
        <Button variant="glass">Edit Details</Button>
      </ModalTrigger>
      <ModalContent variant="matte">
        <ModalHeader>
          <ModalTitle>Account Settings</ModalTitle>
          <ModalDescription>Update your display name and email address.</ModalDescription>
        </ModalHeader>
        <div className="space-y-3 py-1">
          <div className="space-y-1">
            <Label htmlFor="modal-name">Display Name</Label>
            <Input id="modal-name" variant="glass" placeholder="Your name" />
          </div>
          <div className="space-y-1">
            <Label htmlFor="modal-email">Email</Label>
            <Input id="modal-email" type="email" variant="glass" placeholder="you@example.com" />
          </div>
        </div>
        <ModalFooter>
          <ModalClose asChild>
            <Button variant="ghost">Cancel</Button>
          </ModalClose>
          <Button variant="liquid">Save changes</Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  )
}

Frosted Variant

Use `variant="frosted"` for a heavier frost with stronger blur and inner glow. Great for prominent dialogs.

import { Button, Modal, ModalTrigger, ModalClose, ModalContent, ModalHeader, ModalFooter, ModalTitle, ModalDescription } from "@glinui/ui"

export function ModalFrostedDemo() {
  return (
    <Modal>
      <ModalTrigger asChild>
        <Button variant="frosted">Frosted Modal</Button>
      </ModalTrigger>
      <ModalContent variant="frosted">
        <ModalHeader>
          <ModalTitle>Frosted Glass</ModalTitle>
          <ModalDescription>Heavy frost with 40px blur, 200% saturation, and inner glow.</ModalDescription>
        </ModalHeader>
        <ModalFooter>
          <ModalClose asChild>
            <Button variant="ghost">Close</Button>
          </ModalClose>
          <Button variant="liquid">Continue</Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  )
}

Confirmation Dialog

Use a short `ModalDescription` and a destructive action button for irreversible operations.

import { Button, Modal, ModalTrigger, ModalClose, ModalContent, ModalHeader, ModalFooter, ModalTitle, ModalDescription } from "@glinui/ui"

export function ModalConfirmDemo() {
  return (
    <Modal>
      <ModalTrigger asChild>
        <Button variant="matte">Delete Project</Button>
      </ModalTrigger>
      <ModalContent variant="glass" size="sm">
        <ModalHeader>
          <ModalTitle>Are you sure?</ModalTitle>
          <ModalDescription>
            This action cannot be undone. The project and all its data will be permanently deleted.
          </ModalDescription>
        </ModalHeader>
        <ModalFooter>
          <ModalClose asChild>
            <Button variant="ghost">Cancel</Button>
          </ModalClose>
          <Button variant="liquid">Delete</Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  )
}

Accessibility

  • Built on Radix Dialog with focus trapping.
  • ModalTitle is required for screen readers.
  • Background interaction blocked when modal.
  • Focus is trapped inside the dialog while open.
  • `Escape` closes the dialog and returns focus to the trigger.
  • The backdrop overlay blocks interaction with background content.

Keyboard Interactions

KeyAction
EscapeClose the modal.
TabCycle focus within the modal.

ARIA Attributes

  • `role="dialog"`
  • `aria-modal="true"`
  • `aria-labelledby`
  • `aria-describedby`

Reduced Motion

Open/close transitions respect `prefers-reduced-motion`.

The overlay fade and content scale animations respect `prefers-reduced-motion` and collapse to instant transitions.

Affected properties

opacitytransform

API Reference

PropTypeRequiredDefaultDescription
openboolean--Controlled open state.
defaultOpenboolean--Initial open state.
onOpenChange(open: boolean) => void--Open state change handler.
modalboolean-trueTrap focus and block background interaction.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalTrigger

PropTypeRequiredDefaultDescription
asChildboolean-falseRender as child element.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalContent

PropTypeRequiredDefaultDescription
variantPanelVariantPropNoambient style (glinr)Panel look. Omit it for the ambient design style (glinr shell with a gradient hairline ring, elev-3). `plain` is the shadcn border and shadow, `solid` and `soft` are tonal, `glass` is opt-in and needs a backdrop. In the glinr look `ModalHeader` and `ModalFooter` become raised strips that bleed to the dialog edges. Legacy `default` follows the ambient style, `frosted` maps to `glass`, `matte` to `solid`.
size"sm" | "md" | "lg"NomdDialog width and padding.
containerHTMLElement | nullNo-Portal target. When set, the dialog and its flat dim scrim are positioned inside that element.
classNamestring--Additional class names.
onEscapeKeyDown(event: KeyboardEvent) => void--Called when Escape is pressed.
onPointerDownOutside(event: PointerDownOutsideEvent) => void--Called when clicking outside.

ModalHeader

PropTypeRequiredDefaultDescription
classNamestring--Additional class names.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalTitle

PropTypeRequiredDefaultDescription
classNamestring--Additional class names.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalDescription

PropTypeRequiredDefaultDescription
classNamestring--Additional class names.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalFooter

PropTypeRequiredDefaultDescription
classNamestring--Additional class names.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalClose

PropTypeRequiredDefaultDescription
asChildboolean-falseRender as child element.
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

Source

Import directly from the package or browse the source on GitHub. Click any file to view it.

Import

import { Modal,Dialog } from "@glinui/ui"

Source Files

Dependencies

@glinui/ui

Generated API Snapshot

Beta

Auto-extracted from TypeScript source in packages/ui/src/components/modal.tsx. This section is in beta and may lag behind hand-curated docs. Regenerate with pnpm --filter @glinui/docs api:generate.

Generated: 2026-10-08T01:41:21.546Z · Full index: /docs/api-metadata

Primary Props Type

ModalProps

ModalContentProps

PropTypeRequiredDefaultDescription
containerHTMLElement | nullNo-Element to portal into. When set, overlay and content use absolute positioning scoped to the nearest positioned ancestor.
size"sm" | "md" | "lg"No"md"Variant option from modalSizeVariants.
variantPanelVariantPropNo-Surface look. Omit for the ambient design style (glinr by default). `glass` is opt-in and needs a colourful or photographic backdrop to read as frosted.

ModalDescriptionProps

No explicit fields extracted for this props type.

ModalFooterProps

No explicit fields extracted for this props type.

ModalHeaderProps

No explicit fields extracted for this props type.

ModalOverlayProps

No explicit fields extracted for this props type.

ModalPortalProps

No explicit fields extracted for this props type.

ModalProps

No explicit fields extracted for this props type.

ModalTitleProps

No explicit fields extracted for this props type.