Skip to content
Gx
GitHub

Hover Card

A richer preview on hover or focus.

<hovercard.HoverCard class="text-sm">
  <:trigger><button type="button">@ada</button></:trigger>
  <p class="font-medium">Ada Lovelace</p>
  <p class="text-muted-foreground">First programmer.</p>
</hovercard.HoverCard>

Installation

Run the command in the app module.

Terminal
gx add hover-card

The command writes these files.

File Path in the app
HoverCard.gx ui/hover-card/HoverCard.gx
HoverCard.fixtures.go ui/hover-card/HoverCard.fixtures.go
styles.go ui/hover-card/styles.go

Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.

ui/hover-card/HoverCard.gx
package hovercard

props {
  // Trigger is the element that shows the card on hover or on keyboard focus.
  Trigger  gx.Node
  // Align sets the edge of the trigger that the card lines up with: Start, Center or End.
  Align    Align = Start
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the card.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<span class={gx.Cx("group/hover-card relative inline-flex", p.Class)} {...p.Attrs}>
  {p.Trigger}
  <span class={gx.Cx("invisible absolute top-full z-50 mt-1 w-64 -translate-y-2 scale-95 rounded-md border border-border bg-popover p-4 text-popover-foreground opacity-0 shadow-md outline-hidden transition-[opacity,scale,translate,visibility] delay-300 duration-150 motion-reduce:transition-none group-hover/hover-card:visible group-hover/hover-card:translate-y-0 group-hover/hover-card:scale-100 group-hover/hover-card:opacity-100 group-hover/hover-card:delay-700 group-has-[:focus-visible]/hover-card:visible group-has-[:focus-visible]/hover-card:translate-y-0 group-has-[:focus-visible]/hover-card:scale-100 group-has-[:focus-visible]/hover-card:opacity-100 group-has-[:focus-visible]/hover-card:delay-700", alignClass[p.align()])}>{p.Children}</span>
</span>
ui/hover-card/HoverCard.fixtures.go
package hovercard

import "github.com/alternayte/gx"

func demoCard() gx.Node {
	return gx.Frag(
		gx.El("p", gx.Attrs{{Key: "class", Value: "font-medium"}}, gx.Text("Ada Lovelace")),
		gx.El("p", gx.Attrs{{Key: "class", Value: "text-muted-foreground"}}, gx.Text("First programmer.")),
	)
}

var HoverCardFixtures = gx.Fixtures[HoverCardProps]{
	"User": {
		Class:    "text-sm",
		Trigger:  gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("@ada")),
		Children: demoCard(),
	},
	"Center": {
		Class:    "ml-32 text-sm",
		Align:    Center,
		Trigger:  gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("@ada, centered")),
		Children: demoCard(),
	},
	"End": {
		Class:    "ml-64 text-sm",
		Align:    End,
		Trigger:  gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("@ada, at the end")),
		Children: demoCard(),
	},
}
ui/hover-card/styles.go
package hovercard

import "github.com/alternayte/gx"

// Align is the edge of the trigger that the card lines up with.
type Align string

// The alignments of hovercard.HoverCard. The card has no collision handling,
// so the default keeps it inside the page next to a trigger at the left
// edge.
const (
	Start  Align = "start"
	Center Align = "center"
	End    Align = "end"
)

var alignClass = gx.Enum[Align]{
	Start:  "left-0 origin-top-left",
	Center: "left-[round(50%,1px)] origin-top -translate-x-[round(50%,1px)]",
	End:    "right-0 origin-top-right",
}

// align returns the alignment of one card; a zero value is Start.
func (p HoverCardProps) align() Align {
	if p.Align == "" {
		return Start
	}
	return p.Align
}

The theme must define these tokens: --border, --popover, --popover-foreground.

Usage

<hovercard.HoverCard trigger={gx.Text("@ada")} class="text-sm">
  <p class="font-medium">Ada Lovelace</p>
  <p class="text-muted-foreground">First programmer.</p>
</hovercard.HoverCard>

The card is CSS only. It shows on hover and on keyboard focus. It opens after 700 ms and closes after 300 ms, so the pointer can move from the trigger into the card.

Align lines the card up with the trigger: hovercard.Start (default), hovercard.Center or hovercard.End. The card does not move to stay inside the page, so select the alignment that fits the place of the trigger.

The card fades, zooms and slides in. A user who asks for reduced motion gets no transition.

Examples

User

<hovercard.HoverCard class="text-sm">
  <:trigger><button type="button">@ada</button></:trigger>
  <p class="font-medium">Ada Lovelace</p>
  <p class="text-muted-foreground">First programmer.</p>
</hovercard.HoverCard>

Center

<hovercard.HoverCard class="ml-32 text-sm" align={hovercard.Center}>
  <:trigger><button type="button">@ada, centered</button></:trigger>
  <p class="font-medium">Ada Lovelace</p>
  <p class="text-muted-foreground">First programmer.</p>
</hovercard.HoverCard>

End

<hovercard.HoverCard class="ml-64 text-sm" align={hovercard.End}>
  <:trigger><button type="button">@ada, at the end</button></:trigger>
  <p class="font-medium">Ada Lovelace</p>
  <p class="text-muted-foreground">First programmer.</p>
</hovercard.HoverCard>

API reference

A tag sets a prop by its name with a lower-case first letter: Class is class.

hovercard.HoverCard

Prop Type Default Description
Trigger gx.Node Required Trigger is the element that shows the card on hover or on keyboard focus.
Align Align Start Align sets the edge of the trigger that the card lines up with: Start, Center or End.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the card.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Keep the preview short.
  • Use it for a profile or a link preview.

Don't

  • Do not put a form in a hover card.
  • Do not rely on a hover card for a touch-only device.

Keyboard

Key Action
Tab The card appears when the trigger takes keyboard focus.