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.
gx add hover-cardThe 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.
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>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(),
},
}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. |