Popover
Popover is a neobrutalist React component from Cruise UI. A small panel tied to a button: a title, content and actions; a bottom sheet on phones for longer content. Install it with npx shadcn@latest add @cruiseui/popover.
Install
Terminal
npx shadcn@latest add @cruiseui/popoverFirst time? Installation shows the one-off components.json setup (the @cruiseui registry) and how to turn the theme on.
Example
Corners
"use client";
import { Info } from "lucide-react";
import { CruiseButton } from "@/components/cruise/button";
import { CruisePopover } from "@/components/cruise/popover";
export default function PopoverExample() {
return (
<div className="flex flex-wrap gap-4">
<CruisePopover
title="Delivery fee"
trigger={
<CruiseButton variant="secondary">
<Info aria-hidden="true" />
How is this worked out?
</CruiseButton>
}
actions={(close) => (
<CruiseButton variant="secondary" onClick={close}>
Got it
</CruiseButton>
)}
>
₦1,500 within Lagos Island, ₦2,500 to the Mainland. Free on orders over ₦20,000.
</CruisePopover>
<CruisePopover sheet title="Order details" trigger={<CruiseButton variant="secondary">Order #1042</CruiseButton>}>
Classic Celebration Cake × 1, Cupcake Box (6) × 2. Paid by card on 12 October. Out for delivery.
</CruisePopover>
</div>
);
}
<button type="button" class="brutal btn btn-secondary" aria-expanded="false" aria-controls="fee-pop" popovertarget="fee-pop">How is this worked out?</button>
<div class="popover" id="fee-pop" popover role="dialog" aria-labelledby="fee-pop-title">
<p class="popover-title" id="fee-pop-title">Delivery fee</p>
<div class="popover-body">₦1,500 within Lagos Island, ₦2,500 to the Mainland. Free on orders over ₦20,000.</div>
<div class="popover-actions"><button type="button" class="brutal btn btn-secondary" popovertarget="fee-pop" popovertargetaction="hide">Got it</button></div>
</div>
<!-- The HTML popover attribute gives Esc / outside-click closing; place it under its trigger with CSS anchor positioning or a little JS. -->Needs theme.css. Interactive parts (menus, tabs, drawers) also need the script in the spec for that component.
When to use it
When to use
Extra detail or a quick action next to what it's about: how a fee is worked out, a share menu, a short form.
When not to
Hints on hover (use a Tooltip). Anything that needs the whole screen's attention (use a Modal).
Props
CruisePopover
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactElement<unknown, string | JSXElementConstructor<any>> | — | The button that opens it (gets aria-expanded / aria-controls). |
title? | ReactNode | — | — |
children | ReactNode | — | — |
actions? | ReactNode | ((close: () => void) => ReactNode) | — | Buttons under the content; a function gets `close`. |
arrow? | boolean | true | — |
side? | "left" | "right" | "top" | "bottom" | "bottom" | — |
align? | "center" | "start" | "end" | "center" | — |
sheet? | boolean | false | Below 640px, open as a bottom sheet instead (for long content). |
open? | boolean | — | — |
onOpenChange? | ((open: boolean) => void) | — | — |
popupClassName? | string | — | — |
Accessibility
- Opens on click; Esc and a click outside close it.
- Focus moves into the popover and back to the trigger when it closes.
- The trigger has aria-expanded and aria-controls; the title names the popover.