Dismissible
Wrap anything with a close button, with an optional auto-dismiss timeout.
Last updated
Uses JavaScript
A document-level click listener handles every close button: it dispatches a
cancelable apogee:dismiss event on the wrapper and, unless you cancel it,
removes the element. An island arms the timeout.
Without JS: The close button is hidden and the content stays put; a timeout never fires.
Saved
Show codeHide code
<Dismissible>
<Alert color="positive" title="Saved">Your changes are live.</Alert>
</Dismissible>Add
npx @ava-ui/apogee add dismissible
Depends on utils.
Usage
---
import Dismissible from '@/components/apogee/Dismissible/Dismissible.astro'
---
<Dismissible timeout={8000} closeLabel="Hide banner">
<div class="rounded-lg bg-primary-faded p-4 pe-10">Try the new editor.</div>
</Dismissible>
<script>
document.addEventListener('apogee:dismiss', (e) => {
// e.preventDefault() keeps the element; otherwise it is removed.
localStorage.setItem('banner-dismissed', '1')
})
</script>
Leave room for the button at the end edge of your content (pe-10).
API
@composable. Any other native attribute passes through to the wrapper.
| Prop | Type | Default | Description |
|---|---|---|---|
timeout |
number |
— | Auto-dismiss after this many milliseconds. |
closeLabel |
string |
'Dismiss' |
Accessible name of the close button. |
align |
'top' | 'center' |
'top' |
Where the close button sits on the end edge. |
variant |
'default' | 'media' |
'default' |
media gives the button an opaque pill for use over images. |
hideClose |
boolean |
false |
No close button; timeout and your own apogee:dismiss still work. |
class |
string |
— | Extra classes on the wrapper, merged last. |
Variants
Manual
Timed
Show codeHide code
<Dismissible>…</Dismissible>
<Dismissible timeout={5000}>…</Dismissible>Align, media and hideClose
Centred close
No close button
Show codeHide code
<Dismissible align="center">…</Dismissible>
<Dismissible variant="media"><img … /></Dismissible>
<Dismissible hideClose timeout={8000}>…</Dismissible>States
- Open — the content shows with a close button (hidden without JS).
- Dismissed — the close button, a
timeout, or your ownapogee:dismissevent removes the element. CallpreventDefault()on the event to keep it.
Examples
Your own close button: hideClose, then any element with data-dismiss inside closes it (hide it without JS with no-js:hidden). Banner does this to place the button in its layout:
Show codeHide code
<Dismissible hideClose class="flex items-center justify-between gap-3 rounded-lg border border-neutral p-3">
<span class="text-sm">Close me from my own button.</span>
<CloseButton data-dismiss label="Dismiss" size="small" class="no-js:hidden" />
</Dismissible>A promo image the user can close. variant="media" gives the button an opaque pill so it reads on any photo:

Show codeHide code
<Dismissible variant="media" closeLabel="Hide promotion" class="max-w-sm overflow-hidden rounded-lg">
<Image src={placeholder} alt="Autumn sale, 20% off" width={384} height={216} />
</Dismissible>Accessibility
- The close button is a real
<button>with anaria-label; setcloseLabelto name what is being closed. - A timed dismissal removes content without user action. Keep timeouts for non-essential messages, and never for errors.
- Focus is not moved on dismiss; if the button had focus, the browser returns it to the body. Manage it yourself in the
apogee:dismisshandler when that matters.
Tokens
| Token | Used for |
|---|---|
--foreground-neutral-faded / --foreground-neutral |
Button icon default / hover |
--background-neutral-faded-highlighted |
Button hover fill |
--outline-color-focus |
Focus outline |
Do / Don’t
- Do persist the dismissal when the content is a banner the user should not see again.
- Don’t put a form inside a timed Dismissible.
Content
- Set
closeLabelto say what closes (“Close cookie notice”) when there are several on a page. - Keep timed content short enough to read before it leaves: about one sentence per five seconds.