Dismissible
FeedbackManual contextual notice dismissal with controlled state and optional focus return.
Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.
"use client";
import { Dismissible } from "@/components/ui/dismissible";
import { Button } from "@dashboardpack/core/components/ui/button";
import { useRef, useState } from "react";
export default function DismissibleExample() {
const [open, setOpen] = useState(true);
const restore = useRef<HTMLButtonElement>(null);
return (
<div className="w-full max-w-lg space-y-5">
<Dismissible
title="Your workspace is ready"
tone="success"
open={open}
onOpenChange={setOpen}
focusReturnRef={restore}
>
<p>Invite your team and give your next project a considered home.</p>
</Dismissible>
<Button
ref={restore}
type="button"
variant="outline"
onClick={() => setOpen(true)}
>
Show workspace notice
</Button>
<Dismissible title="A small tip">
<p>You can change the workspace density from the appearance menu.</p>
</Dismissible>
<p role="status" className="text-xs text-muted-foreground">
{open
? "Workspace notice visible."
: "Workspace notice dismissed. Use Show workspace notice to bring it back."}
</p>
</div>
);
}Usage
Use for a notice or tip that people may dismiss when they have read it. Own open/onOpenChange or initialize internal state with defaultOpen. Dismissal has no timer, persistence, or backend effect. Supply a ref to a focusable control outside the notice when keyboard dismissal should return there; focus is scheduled after the removal commit. Restoration belongs to the caller, as shown by the preview button.
import { Dismissible } from "@/components/ui/dismissible";Shipped as editable source in src/components/ui/dismissible.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.
Component source
The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.
src/components/ui/dismissible.tsx
"use client";
import { useState, type ReactNode, type RefObject } from "react";
import { X, CheckCircle2, Info } from "lucide-react";
import { cn } from "@dashboardpack/core/lib/utils";
export interface DismissibleProps {
title: string;
children: ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
focusReturnRef?: RefObject<HTMLElement | null>;
tone?: "neutral" | "success";
className?: string;
}
export function Dismissible({
title,
children,
open,
defaultOpen = true,
onOpenChange,
focusReturnRef,
tone = "neutral",
className,
}: DismissibleProps) {
const [internal, setInternal] = useState(defaultOpen);
if (!(open ?? internal)) return null;
const Icon = tone === "success" ? CheckCircle2 : Info;
return (
<section
aria-label={title}
className={cn(
"flex min-w-0 items-start gap-3 rounded-xl border bg-card p-4",
tone === "success" && "border-success/30 bg-success/5",
className,
)}
>
<Icon
aria-hidden
className={cn(
"mt-0.5 size-5 shrink-0",
tone === "success" ? "text-success" : "text-muted-foreground",
)}
/>
<div className="min-w-0 flex-1 space-y-1">
<h3 className="text-sm font-semibold">{title}</h3>
<div className="text-sm leading-6 text-muted-foreground">
{children}
</div>
</div>
<button
type="button"
aria-label={`Dismiss ${title}`}
onClick={() => {
setInternal(false);
onOpenChange?.(false);
requestAnimationFrame(() => focusReturnRef?.current?.focus());
}}
className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
>
<X className="size-4" />
</button>
</section>
);
}API reference
Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.
| Prop | Type / Default | Description |
|---|---|---|
| title | stringDefault: required | Visible heading, section name, and close-action context. |
| children | ReactNodeDefault: required | Readable notice content. |
| open / defaultOpen | booleanDefault: internal true | Controlled state or initial internal visibility. |
| onOpenChange | (open: boolean) => voidDefault: — | Observe dismissal and apply controlled state. |
| focusReturnRef | RefObject<HTMLElement | null>Default: — | Focusable control outside the notice to receive focus after dismissal. |
| tone | neutral | successDefault: neutral | Notice treatment. |
| className | stringDefault: — | Extend the container with semantic theme utilities. |