T templcn/ui
Componentpopover

Popover

Displays rich content in a portal, triggered by a button.

Preview

Displays rich content in a portal, triggered by a button.

Installation

Add this component to your project using the CLI.

templcn add popover

Usage

1. Import the component in your Go or templ file:

import "your-module/ui"

2. Use the component in your template:

@ui.Popover(ui.PopoverProps{}) {
  @ui.PopoverTrigger(ui.DOMProps{}) {
    @ui.Button(ui.ButtonProps{Variant: ui.ButtonVariantOutline}) { Open popover }
  }
  @ui.PopoverContent(ui.DOMProps{Class: "w-80"}) {
    <div class="grid gap-4">
      <div class="space-y-2">
        <h4 class="font-medium leading-none">Dimensions</h4>
        <p class="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
      </div>
    </div>
  }
}

Composition

Use these parts together to build the component.

Popover
├── PopoverTrigger
├── Button
├── PopoverContent

Examples

Named examples and common variations.

Default

Anchored popover panel with custom content.

Popover content
API Reference
PropTypeDefaultDescription
Sidestring"bottom"Preferred placement side.
Alignstring"center"Alignment along the axis.
ModalboolfalseWhether to trap focus.
View source
package ui

import (
	"context"
	"io"

	"github.com/a-h/templ"
)

type PopoverProps struct {
	DOMProps
	Open        bool
	DefaultOpen bool
	Side        string
	Align       string
	SideOffset  string
	AlignOffset string
	Modal       bool
}

func Popover(props PopoverProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		open := props.Open || props.DefaultOpen
		attrs := attrsFromDOMProps(props.DOMProps, "popover", "relative inline-block")
		attrs["data-state"] = openState(open)
		if open {
			attrs["data-open"] = "true"
		}
		if props.DefaultOpen {
			attrs["data-default-open"] = "true"
		}
		if props.Side != "" {
			attrs["data-side"] = props.Side
		}
		if props.Align != "" {
			attrs["data-align"] = props.Align
		}
		if props.SideOffset != "" {
			attrs["data-side-offset"] = props.SideOffset
		}
		if props.AlignOffset != "" {
			attrs["data-align-offset"] = props.AlignOffset
		}
		if props.Modal {
			attrs["data-modal"] = "true"
		}
		ctx = context.WithValue(ctx, floatingRenderStateKey{}, floatingRenderState{open: open})
		return renderElement(ctx, w, "div", attrs, templ.GetChildren(ctx))
	})
}

func PopoverTrigger(props DOMProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		attrs := attrsFromDOMProps(props, "popover-trigger", "")
		if _, ok := attrs["type"]; !ok {
			attrs["type"] = "button"
		}
		if _, ok := attrs["aria-haspopup"]; !ok {
			attrs["aria-haspopup"] = "dialog"
		}
		if _, ok := attrs["aria-expanded"]; !ok {
			attrs["aria-expanded"] = "false"
		}
		return renderElement(ctx, w, "button", attrs, templ.GetChildren(ctx))
	})
}
func PopoverAnchor(props DOMProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		return renderElement(ctx, w, "div", attrsFromDOMProps(props, "popover-anchor", ""), templ.GetChildren(ctx))
	})
}
func PopoverContent(props DOMProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		attrs := attrsFromDOMProps(props, "popover-content", "absolute left-0 top-full z-50 mt-1 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95")
		if _, ok := attrs["data-state"]; !ok {
			attrs["data-state"] = floatingStateFromContext(ctx)
		}
		if _, ok := attrs["data-side"]; !ok {
			attrs["data-side"] = "bottom"
		}
		if _, ok := attrs["tabindex"]; !ok {
			attrs["tabindex"] = "-1"
		}
		if !floatingOpenFromContext(ctx) {
			attrs["hidden"] = true
		}
		return renderElement(ctx, w, "div", attrs, templ.GetChildren(ctx))
	})
}
func PopoverHeader(props DOMProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		return renderElement(ctx, w, "div", attrsFromDOMProps(props, "popover-header", "flex flex-col gap-1 text-sm"), templ.GetChildren(ctx))
	})
}
func PopoverTitle(props DOMProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		return renderElement(ctx, w, "div", attrsFromDOMProps(props, "popover-title", "font-medium"), templ.GetChildren(ctx))
	})
}
func PopoverDescription(props DOMProps) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		return renderElement(ctx, w, "p", attrsFromDOMProps(props, "popover-description", "text-muted-foreground"), templ.GetChildren(ctx))
	})
}