<div data-controller="demo">
<div class="flex flex-col items-center gap-3">
<div class="<%= glass_class(shape: :hud, extra: "flex items-center gap-2 py-3 px-4 text-sm font-semibold") %>" role="status"><%= form_alert_icon(tone: :notice) %><span>Changes saved</span></div>
<button type="button" class="<%= button_class(variant: :secondary) %>" data-action="demo#toast">Show a live toast</button>
<template data-demo-target="toast">
<div class="<%= glass_class(shape: :hud, extra: "flex items-center gap-2 py-3 px-4 text-sm font-semibold") %> pointer-events-auto" role="status" data-controller="toast"><%= form_alert_icon(tone: :notice) %><span>Changes saved</span></div>
</template>
<div class="pointer-events-none fixed inset-x-0 bottom-6 z-nav-popover flex justify-center" aria-live="polite" data-demo-target="toasts"></div>
</div>
</div>
Interaction controller
app/javascript/controllers/demo_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["toast", "toasts"]
press({ currentTarget }) {
currentTarget.setAttribute(
"aria-pressed",
String(currentTarget.getAttribute("aria-pressed") !== "true"),
)
}
select({ currentTarget }) {
for (const tab of currentTarget.closest("[role=tablist]").querySelectorAll("[role=tab]")) {
tab.setAttribute("aria-selected", String(tab === currentTarget))
}
}
choose({ currentTarget }) {
for (const button of currentTarget.closest("[role=group]").querySelectorAll("button")) {
button.setAttribute("aria-pressed", String(button === currentTarget))
}
}
toast() {
this.toastsTarget.append(this.toastTarget.content.cloneNode(true))
}
}