Alert
An alert dialog that interrupts the current flow until the user makes an explicit choice, such as confirming a destructive action.
Looking for a status banner? Chassis calls inline status messages
Notifications. An Alert is chassis-css's
alert dialog: it blocks the page until the
user answers.
Overview
<Alert> renders a <dialog> with role="alertdialog" and opens it as a modal dialog, the way
Modal does. Use it for a decision the flow can't continue
without: confirming a destructive action, acknowledging an error. For an overlay that doesn't
demand a decision, use Modal; for a message that doesn't interrupt, use Notification.
An alert waits for an explicit choice, so by default neither a click on the backdrop nor the
Escape key closes it; the alert bounces instead. Set backdrop and keyboard to allow them.
Basic usage
Compose an <AlertIcon> (optional), an <AlertBody> with an <AlertTitle> and an <AlertText>,
and an <AlertFooter> with the actions. The footer lays the actions out right to left, so write
the primary action first. <AlertCancel> is the least destructive action: it closes the alert, and
it has focus when the alert opens.
import { useState } from 'react'
import {
Alert,
AlertBody,
AlertCancel,
AlertFooter,
AlertIcon,
AlertText,
AlertTitle,
Button
} from '@chassis-ui/react'
export const Example = () => {
const [visible, setVisible] = useState(false)
return (
<>
<Button color="danger" onClick={() => setVisible(true)}>
Delete file
</Button>
<Alert visible={visible} onVisibleChange={setVisible}>
<AlertIcon name="exclamation-triangle-solid" color="danger" />
<AlertBody>
<AlertTitle>Delete the file?</AlertTitle>
<AlertText>This file will be removed for everyone. This can't be undone.</AlertText>
</AlertBody>
<AlertFooter>
<Button color="danger" onClick={() => setVisible(false)}>
Delete file
</Button>
<AlertCancel>Cancel</AlertCancel>
</AlertFooter>
</Alert>
</>
)
}Error codes
<AlertCode> shows a machine-readable reference under the title, in subdued type.
import { useState } from 'react'
import {
Alert,
AlertBody,
AlertCancel,
AlertCode,
AlertFooter,
AlertIcon,
AlertText,
AlertTitle,
Button
} from '@chassis-ui/react'
export const Example = () => {
const [visible, setVisible] = useState(false)
return (
<>
<Button onClick={() => setVisible(true)}>Upload</Button>
<Alert visible={visible} onVisibleChange={setVisible}>
<AlertIcon name="exclamation-circle-solid" color="danger" />
<AlertBody>
<AlertTitle>Upload failed</AlertTitle>
<AlertCode>ERR-1234</AlertCode>
<AlertText>
The server refused the file. Try again, or contact support with the code above.
</AlertText>
</AlertBody>
<AlertFooter>
<AlertCancel>Close</AlertCancel>
</AlertFooter>
</Alert>
</>
)
}Close button
An alert's actions are usually its only way out. When it needs one without an action of its own,
closeButton adds a close button in the corner.
import { useState } from 'react'
import { Alert, AlertBody, AlertFooter, AlertText, AlertTitle, Button } from '@chassis-ui/react'
export const Example = () => {
const [visible, setVisible] = useState(false)
return (
<>
<Button onClick={() => setVisible(true)}>Show alert</Button>
<Alert closeButton closeLabel="Close alert" visible={visible} onVisibleChange={setVisible}>
<AlertBody>
<AlertTitle>Your session is about to expire</AlertTitle>
<AlertText>
Renew it to keep working, or close this and be signed out in 2 minutes.
</AlertText>
</AlertBody>
<AlertFooter>
<Button color="primary" onClick={() => setVisible(false)}>
Renew session
</Button>
</AlertFooter>
</Alert>
</>
)
}Footer layout
On small screens the actions share one row. stacked stacks them as full-width rows instead. Push
a separate action, such as a destructive one, to the other end with className="sm:me-auto".
import { useState } from 'react'
import {
Alert,
AlertBody,
AlertCancel,
AlertFooter,
AlertIcon,
AlertText,
AlertTitle,
Button
} from '@chassis-ui/react'
export const Example = () => {
const [visible, setVisible] = useState(false)
return (
<>
<Button onClick={() => setVisible(true)}>Leave page</Button>
<Alert visible={visible} onVisibleChange={setVisible}>
<AlertIcon name="info-circle-solid" />
<AlertBody>
<AlertTitle>Save changes?</AlertTitle>
<AlertText>Do you want to save your changes before leaving the page?</AlertText>
</AlertBody>
<AlertFooter stacked>
<Button color="primary" onClick={() => setVisible(false)}>
Save changes
</Button>
<AlertCancel>Cancel</AlertCancel>
<Button
className="sm:me-auto"
color="danger"
variant="outline"
onClick={() => setVisible(false)}
>
Discard
</Button>
</AlertFooter>
</Alert>
</>
)
}Sequential alerts
One alert can lead to another: open the next from an action of the current one. The second alert
below asks for a typed confirmation, and its field has focus when it opens, because it carries
data-autofocus.
import { useState } from 'react'
import {
Alert,
AlertBody,
AlertCancel,
AlertFooter,
AlertIcon,
AlertText,
AlertTitle,
Button,
TextInput
} from '@chassis-ui/react'
export const Example = () => {
const [step, setStep] = useState<'none' | 'ask' | 'confirm'>('none')
const [typed, setTyped] = useState('')
const close = () => {
setStep('none')
setTyped('')
}
return (
<>
<Button color="danger" onClick={() => setStep('ask')}>
Delete account
</Button>
<Alert visible={step === 'ask'} onClose={close}>
<AlertIcon name="exclamation-triangle-solid" color="danger" />
<AlertBody>
<AlertTitle>Delete your account?</AlertTitle>
<AlertText>All of your data will be removed.</AlertText>
</AlertBody>
<AlertFooter>
<Button color="danger" onClick={() => setStep('confirm')}>
Delete account
</Button>
<AlertCancel>Cancel</AlertCancel>
</AlertFooter>
</Alert>
<Alert visible={step === 'confirm'} onClose={close}>
<AlertIcon name="exclamation-triangle-solid" color="danger" />
<AlertBody>
<AlertTitle>Final confirmation</AlertTitle>
<AlertText>
This is permanent. Type <strong>DELETE</strong> to confirm.
</AlertText>
<TextInput
aria-label="Type DELETE to confirm"
data-autofocus=""
value={typed}
onChange={setTyped}
/>
</AlertBody>
<AlertFooter>
<Button color="danger" disabled={typed !== 'DELETE'} onClick={close}>
Confirm delete
</Button>
<AlertCancel>Cancel</AlertCancel>
<Button className="sm:me-auto" variant="link" onClick={() => setStep('ask')}>
Go back
</Button>
</AlertFooter>
</Alert>
</>
)
}Accessibility
<Alert> names itself with its <AlertTitle> (aria-labelledby) and describes itself with its
<AlertCode> and <AlertText> (aria-describedby), the two an alertdialog needs. Each part is
listed under the id it has on the page (your own id, or the child's under asChild), in page
order, and only while it is rendered. So both attributes are added once the alert has mounted,
never in the server's HTML. Set your own aria-labelledby or aria-describedby to point
elsewhere.
When the alert opens, focus moves to its first element with data-autofocus: <AlertCancel> has
it, so the least destructive action is focused, as the
WAI-ARIA pattern recommends. Give an
earlier element data-autofocus to focus it instead, or opt <AlertCancel> out with
data-autofocus="false". React's autoFocus isn't enough: in a page rendered in the browser,
React writes no autofocus attribute for the dialog to find.
When the alert closes, focus returns to the element that had it when the alert opened. After a chain of alerts, that is the element that opened the first one.
API
Alert
| Prop | Type | Default |
|---|---|---|
backdrop? | true | "static" | static |
Whether a click on the backdrop closes the alert. By default it doesn't ('static'): the
alert bounces, since an alert dialog waits for an explicit choice. Set true to let it close. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
closeButton? | boolean | — |
| Adds a close button in the corner, for an alert that needs a way out besides its actions. | ||
closeLabel? | string | — |
The close button's accessible name. Defaults to 'Close'; set it for non-English UIs. | ||
defaultVisible? | boolean | — |
Whether the alert is open when it first renders. Such an alert is uncontrolled: it closes
itself on a close request. Use visible to open and close it from outside. | ||
instant? | boolean | — |
| Disable the open/close transition entirely. | ||
keyboard? | boolean | false |
| Whether the Escape key closes the alert. By default it doesn't: the alert bounces instead. | ||
onClose? | (() => void) | — |
Callback fired when the alert asks to be closed: AlertCancel, the close button, and, when
allowed, Escape or a backdrop click. | ||
onClosePrevented? | (() => void) | — |
Callback fired when a close attempt is blocked: a click on a static backdrop, or Escape
while keyboard is off. | ||
onHidden? | (() => void) | — |
| Callback fired after the exit transition completes and the alert is fully hidden. | ||
onShow? | (() => void) | — |
| Callback fired when the alert starts to open. | ||
onShown? | (() => void) | — |
| Callback fired after the entry transition completes and the alert is fully visible. | ||
onVisibleChange? | ((visible: boolean) => void) | — |
Callback fired with false when the alert asks to be closed, at the same moments as
onClose. It makes visible and onVisibleChange a pair that takes a state setter. | ||
open? | boolean | — |
Renders the alert open from its first paint, server HTML included, as a non-modal dialog: a
static preview. It stays open until a close request. Use visible or defaultVisible to
open it as a modal dialog. | ||
visible? | boolean | — |
Whether the alert is open. The alert is then controlled: a close request only fires onClose
and onVisibleChange, and the alert closes when this becomes false. | ||
AlertIcon
| Prop | Type | Default |
|---|---|---|
className? | string | — |
| A string of all className you want applied to the component. | ||
font? | boolean | — |
name | string | — |
Icon name, e.g. folder-tree. Matches an id in the SVG sprite, or a cx-{name} font glyph
class in font mode (see IconProvider's fontPrefix). | ||
size? | string | number | — |
Icon size: a number of pixels, or any CSS length ('1.25rem', '1em'). Sets chassis-css's
--cx-icon-size on the icon, so it applies in font mode too. Leave unset to keep the size
chassis-css gives icons in context (inside a button, an input, a navbar toggler, ...). | ||
sprite? | string | — |
URL of the SVG sprite file. Defaults to IconProvider's sprite, or to none — a sprite
embedded in the page, referenced as #name. Ignored in font mode. | ||
title? | string | — |
| Accessible name. When set, the icon is exposed to assistive tech instead of hidden. | ||
AlertBody
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
component? | ElementType | 'div' |
| Component used for the root node. Either a string to use an HTML element or a component. | ||
ref? | any | — |
AlertTitle
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
component? | ElementType | 'h2' |
| Component used for the root node. Either a string to use an HTML element or a component. | ||
ref? | any | — |
AlertCode
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
component? | ElementType | 'code' |
| Component used for the root node. Either a string to use an HTML element or a component. | ||
ref? | any | — |
AlertText
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
component? | ElementType | 'p' |
| Component used for the root node. Either a string to use an HTML element or a component. | ||
ref? | any | — |
AlertFooter
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
component? | ElementType | 'div' |
| Component used for the root node. Either a string to use an HTML element or a component. | ||
ref? | any | — |
stacked? | boolean | — |
| Stacks the actions as full-width rows on small screens, instead of sharing one row. | ||
AlertCancel
| Prop | Type | Default |
|---|---|---|
className? | string | — |
| A string of all className you want applied to the base component. | ||
color? | ContextColor | default |
| Sets the color of the component to one of Chassis context colors. | ||
disabled? | boolean | — |
| Toggle the disabled state for the component. | ||
onClick? | MouseEventHandler<HTMLButtonElement | HTMLAnchorElement | HTMLInputElement> | — |
Fires on click. Typed for every element component can actually render (button, a,
input, or a custom component), rather than narrowed to HTMLButtonElement alone. | ||
pressed? | boolean | — |
Marks the button as pressed for toggle-style usage (e.g. a formatting toolbar button).
Applies the .active class and sets aria-pressed so assistive technology announces
"button, pressed" rather than treating the button as a navigation link. A link (href, or
an <a> as component or the asChild element) has no pressed state to announce: it gets
the class alone, unless you give it role="button". | ||
shape? | Shapes | — |
| Select the shape of the component. | ||
size? | "sm" | "lg" | — |
| Size the component sm or lg. | ||
type? | "button" | "submit" | "reset" | — |
Specifies the type of button. Always specify the type attribute for the <button> element.
Different browsers may use different default types for the <button> element. | ||
variant? | "link" | "basic" | "outline" | "smooth" | — |
Set the button style variant. Same as ContextStyle, but solid (the unmodified default
look) doesn't apply as a class, and link — button-specific, not a context color — makes
the button look and behave like a hyperlink while keeping its color. | ||