Kosmesis
Components

Popover

A click-toggled floating panel, wrapping Morphos's Popover primitive.

Installation

npx kosmesis add popover
pnpm dlx kosmesis add popover
yarn dlx kosmesis add popover
bunx kosmesis add popover

Install the following dependencies:

npm install @morphos/overlays
pnpm add @morphos/overlays
yarn add @morphos/overlays
bun add @morphos/overlays

Copy and paste the following code into your project.

popover.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { PopoverContent as MorphosPopoverContent, type PopoverContentProps as MorphosPopoverContentProps  } from "@morphos/overlays";import { cn } from "@/lib/utils";/** * `Popover` and `PopoverTrigger` are re-exported directly — same reasoning as `Tooltip`: the root * is always instantiated directly (`@State() popover = new Popover()`), never mounted via JSX, so * wrapping it would break `.isOpen`/`.toggle()`/`.closePopover()`. */export {  Popover,  PopoverTrigger,  type PopoverProps,  type PopoverTriggerProps,} from "@morphos/overlays";export type PopoverContentProps = MorphosPopoverContentProps;@Component()export class PopoverContent extends StatelessComponent<PopoverContentProps> {  render() {    const { class: cls, ...rest } = this.props;    return (      <MorphosPopoverContent        class={cn(          "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",          "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95",          cls,        )}        {...rest}      />    );  }}

Install the following dependencies:

npm install @morphos/overlays @praxisjs/css
pnpm add @morphos/overlays @praxisjs/css
yarn add @morphos/overlays @praxisjs/css
bun add @morphos/overlays @praxisjs/css

Copy and paste the following code into your project.

popover.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, keyframes, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { PopoverContent as MorphosPopoverContent, type PopoverContentProps as MorphosPopoverContentProps  } from "@morphos/overlays";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);const popIn = keyframes("kosmesis-pop-in", {  from: { opacity: "0", transform: "scale(0.95)" },  to: { opacity: "1", transform: "scale(1)" },});class PopoverStyles extends Stylesheet {  $content = this.css({    position: "relative",    zIndex: 50,    width: "18rem",    borderRadius: `calc(${t.radius} - 2px)`,    border: `1px solid ${t.border}`,    backgroundColor: t.popover,    padding: "1rem",    color: t.popoverForeground,    boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)",    outline: "none",  }).on("&[data-open]", { animation: `${popIn} 100ms ease-out` });}/** * `Popover` and `PopoverTrigger` are re-exported directly — same reasoning as `Tooltip`: the root * is always instantiated directly (`@State() popover = new Popover()`), never mounted via JSX, so * wrapping it would break `.isOpen`/`.toggle()`/`.closePopover()`. */export { Popover, PopoverTrigger, type PopoverProps, type PopoverTriggerProps } from "@morphos/overlays";export type PopoverContentProps = MorphosPopoverContentProps;@Component()export class PopoverContent extends StatelessComponent<PopoverContentProps> {  @Styled(PopoverStyles) $s!: PopoverStyles;  render() {    const { class: cls, ...rest } = this.props;    return <MorphosPopoverContent class={cx(this.$s.$content, cls)} {...rest} />;  }}

Examples

Usage

import { Popover, PopoverContent, PopoverTrigger } from "@morphos/overlays";
import { buttonVariants } from "@/components/ui/button";

@Component()
class Settings extends StatefulComponent {
  @State() popover = new Popover();

  render() {
    return (
      <>
        <PopoverTrigger popover={this.popover} class={buttonVariants({ variant: "outline" })}>
          Open popover
        </PopoverTrigger>
        <PopoverContent popover={this.popover}>
          <p>Place any content here.</p>
        </PopoverContent>
      </>
    );
  }
}

Morphos's PopoverTrigger always renders its own <button> — there's no asChild merge like Radix's. Style it directly with buttonVariants() (imported from ./button) instead of nesting a separate Button inside it, which would produce invalid nested <button> elements.

Props

Popover

Re-exported directly from @morphos/overlays.

PropTypeDefault
openboolean— controlled open state
defaultOpenboolean
onOpenChange(open: boolean) => void
closeOnEscapeboolean
closeOnOutsideClickboolean
side"top" | "right" | "bottom" | "left""bottom"
align"start" | "center" | "end""start"
sideOffsetnumber4

PopoverTrigger

PropTypeDefault
popoverPopover— required

PopoverContent

PropTypeDefault
popoverPopover— required
aria-labelstring
aria-labelledbystring

On this page