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

Your changes are live.
Show 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

Stays until you close it.
Show code
<Dismissible>…</Dismissible>
<Dismissible timeout={5000}>…</Dismissible>

Align, media and hideClose

Centred close

The button sits mid-height.

No close button

Dismissed by a timeout or your own event.
Show 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 own apogee:dismiss event removes the element. Call preventDefault() 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:

Close me from my own button.
Show 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:

Autumn sale, 20% off
Show 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 an aria-label; set closeLabel to 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:dismiss handler 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 closeLabel to 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.