Modal
Flexible modal dialogs with multiple patterns and state management options.
Basic Modal
Basic Modal Usage
import { Modal, Button } from '@creo-team/buzz-ui'
import { useState } from 'react'
export default function BasicExample() {
const [open, setOpen] = useState(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Modal
isOpen={open}
onClose={() => setOpen(false)}
header="Basic Modal"
>
<p>This is a basic modal with header and close functionality.</p>
</Modal>
</>
)
}Query Parameter State Management
π‘ Recommended Pattern
Using query parameters for modal state allows for shareable URLs, browser back/forward navigation, and better user experience.
Check the URL when opened!
Query Parameter Modal
import { Modal, Button } from '@creo-team/buzz-ui'
import { useSearchParams, useRouter } from 'next/navigation'
export default function QueryParamModal() {
const searchParams = useSearchParams()
const router = useRouter()
const [isClient, setIsClient] = useState(false)
// Prevent SSR issues
useEffect(() => {
setIsClient(true)
}, [])
const isOpen = isClient && searchParams.get('modal') === 'settings'
const openModal = () => {
if (!isClient) return
const params = new URLSearchParams(searchParams.toString())
params.set('modal', 'settings')
router.push(`?${params.toString()}`)
}
const closeModal = () => {
if (!isClient) return
const params = new URLSearchParams(searchParams.toString())
params.delete('modal')
router.push(`?${params.toString()}`)
}
return (
<>
<Button onClick={openModal}>Open Settings</Button>
<Modal
isOpen={isOpen}
onClose={closeModal}
header="Settings"
>
<div className="space-y-4">
<p>Settings content here...</p>
<p className="text-sm text-[var(--c-text-secondary)]">
π‘ Notice the URL changes and browser back button works!
</p>
</div>
</Modal>
</>
)
}Form Modal
Form Modal Example
import { Modal, Button } from '@creo-team/buzz-ui'
import { useState } from 'react'
export default function FormModal() {
const [open, setOpen] = useState(false)
const [formData, setFormData] = useState({ name: '', email: '' })
const handleSubmit = (e) => {
e.preventDefault()
console.log('Form submitted:', formData)
setOpen(false)
}
return (
<>
<Button onClick={() => setOpen(true)}>Create User</Button>
<Modal
isOpen={open}
onClose={() => setOpen(false)}
header="Create New User"
>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">Name</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="w-full p-2 border rounded"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Email</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
className="w-full p-2 border rounded"
required
/>
</div>
<div className="flex gap-2 pt-4">
<Button type="submit" variant="bold">Create</Button>
<Button type="button" onClick={() => setOpen(false)}>Cancel</Button>
</div>
</form>
</Modal>
</>
)
}Confirmation Modal
Confirmation Modal
import { Modal, Button } from '@creo-team/buzz-ui'
import { useState } from 'react'
export default function ConfirmationModal() {
const [open, setOpen] = useState(false)
const handleDelete = () => {
// Perform delete action
console.log('Item deleted')
setOpen(false)
}
return (
<>
<Button onClick={() => setOpen(true)} variant="danger">
Delete Item
</Button>
<Modal
isOpen={open}
onClose={() => setOpen(false)}
header="Confirm Deletion"
>
<div className="space-y-4">
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
<div className="flex gap-2 pt-4">
<Button onClick={handleDelete} variant="danger">
Delete
</Button>
<Button onClick={() => setOpen(false)}>
Cancel
</Button>
</div>
</div>
</Modal>
</>
)
}Advanced Patterns
Custom Hook for Query Params
Custom Hook Pattern
// Now available as a built-in hook!
import { useModalQuery } from '@creo-team/buzz-ui'
export default function MyComponent() {
const settingsModal = useModalQuery('settings')
const createModal = useModalQuery('create')
return (
<>
<Button onClick={settingsModal.open}>Settings</Button>
<Button onClick={createModal.open}>Create</Button>
<Modal isOpen={settingsModal.isOpen} onClose={settingsModal.close}>
Settings content
</Modal>
<Modal isOpen={createModal.isOpen} onClose={createModal.close}>
Create form
</Modal>
</>
)
}
// The hook handles SSR safety automatically:
// - Prevents "document is not defined" errors
// - Only accesses query params after client hydration
// - Provides clean open/close APIMultiple Modal Management
Multiple Modal Manager
// For managing multiple modals with query params
const MODAL_KEYS = {
SETTINGS: 'settings',
CREATE_USER: 'create-user',
EDIT_PROFILE: 'edit-profile',
CONFIRM_DELETE: 'confirm-delete'
} as const
export function useModals() {
const searchParams = useSearchParams()
const router = useRouter()
const currentModal = searchParams.get('modal')
const openModal = (modalKey: string) => {
const params = new URLSearchParams(searchParams.toString())
params.set('modal', modalKey)
router.push(`?${params.toString()}`)
}
const closeModal = () => {
const params = new URLSearchParams(searchParams.toString())
params.delete('modal')
router.push(`?${params.toString()}`)
}
return {
currentModal,
openModal,
closeModal,
isOpen: (modalKey: string) => currentModal === modalKey
}
}Modal with Data Loading
Data Loading Modal
import { Modal, Button } from '@creo-team/buzz-ui'
import { useState, useEffect } from 'react'
export default function DataModal() {
const [open, setOpen] = useState(false)
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
if (open) {
setLoading(true)
// Load data when modal opens
fetch('/api/user-data')
.then(res => res.json())
.then(setData)
.finally(() => setLoading(false))
}
}, [open])
return (
<>
<Button onClick={() => setOpen(true)}>View Profile</Button>
<Modal isOpen={open} onClose={() => setOpen(false)} header="User Profile">
{loading ? (
<div className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
) : (
<div className="space-y-4">
<h3>Welcome, {data?.name}</h3>
<p>Email: {data?.email}</p>
</div>
)}
</Modal>
</>
)
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen* | boolean | false | Controls modal visibility |
onClose* | () => void | β | Called when modal should close |
header | ReactNode | β | Optional header content |
children | ReactNode | β | Modal body content |
className | string | β | Additional CSS classes |
closeOnBackdrop | boolean | true | Close when clicking backdrop |
Best Practices
β Do
- β’ Use query params for shareable modal states
- β’ Provide clear close affordances
- β’ Keep modal content focused and concise
- β’ Use appropriate modal sizes
- β’ Handle loading states in data modals
β Don't
- β’ Nest modals inside other modals
- β’ Make modals too large or complex
- β’ Forget to handle escape key
- β’ Use modals for simple confirmations
- β’ Block the entire UI unnecessarily