Skip to content
Gx
GitHub

Tooltip

A short hint on hover or focus.

<tooltip.Tooltip content="Add to library"><button type="button">Hover me</button></tooltip.Tooltip>

Installation

Run the command in the app module.

Terminal
gx add tooltip

The command writes these files.

File Path in the app
Tooltip.gx ui/tooltip/Tooltip.gx
Tooltip.fixtures.go ui/tooltip/Tooltip.fixtures.go
styles.go ui/tooltip/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/tooltip/Tooltip.gx
package tooltip

props {
  // Content is the text of the tooltip. An empty value renders no tooltip.
  Content  string = ""
  // Side sets the edge of the trigger that the tooltip appears on: Top, Bottom, Left or Right.
  Side     Side = Top
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the element that shows the tooltip on hover or on keyboard focus.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<span class={gx.Cx("group/tooltip relative inline-flex", p.Class)} {...p.Attrs}>
  {p.Children}
  if p.Content != "" {
    <span role="tooltip" class={gx.Cx("pointer-events-none invisible absolute z-50 w-max max-w-64 rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background opacity-0 scale-95 transition-[opacity,scale,translate,visibility] duration-150 motion-reduce:transition-none group-hover/tooltip:visible group-hover/tooltip:opacity-100 group-hover/tooltip:scale-100 group-has-[:focus-visible]/tooltip:visible group-has-[:focus-visible]/tooltip:opacity-100 group-has-[:focus-visible]/tooltip:scale-100", sideClass[p.side()])}>
      {p.Content}
      <span aria-hidden="true" class={gx.Cx("absolute size-2.5 rotate-45 rounded-[2px] bg-foreground", arrowClass[p.side()])}></span>
    </span>
  }
</span>
ui/tooltip/Tooltip.fixtures.go
package tooltip

import "github.com/alternayte/gx"

var TooltipFixtures = gx.Fixtures[TooltipProps]{
	"Top":    {Content: "Add to library", Children: gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("Hover me"))},
	"Right":  {Content: "Add to library", Side: Right, Children: gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("Hover me"))},
	"Bottom": {Content: "Add to library", Side: Bottom, Children: gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("Hover me"))},
	"Left":   {Content: "Add to library", Side: Left, Children: gx.El("button", gx.Attrs{{Key: "type", Value: "button"}}, gx.Text("Hover me"))},
}
ui/tooltip/styles.go
package tooltip

import "github.com/alternayte/gx"

// Side is the edge a tooltip appears on.
type Side string

// The sides of tooltip.Tooltip.
const (
	Top    Side = "top"
	Bottom Side = "bottom"
	Left   Side = "left"
	Right  Side = "right"
)

// The content sits 5px off the trigger, the height of the arrow. It zooms
// from the arrow and slides 2 units from the trigger side. round() keeps the
// centred content on whole pixels, so its text stays sharp.
var sideClass = gx.Enum[Side]{
	Top:    "bottom-full left-[round(50%,1px)] mb-[5px] origin-bottom -translate-x-[round(50%,1px)] translate-y-2 group-hover/tooltip:translate-y-0 group-has-[:focus-visible]/tooltip:translate-y-0",
	Bottom: "top-full left-[round(50%,1px)] mt-[5px] origin-top -translate-x-[round(50%,1px)] -translate-y-2 group-hover/tooltip:translate-y-0 group-has-[:focus-visible]/tooltip:translate-y-0",
	Left:   "right-full top-[round(50%,1px)] mr-[5px] origin-right -translate-y-[round(50%,1px)] translate-x-2 group-hover/tooltip:translate-x-0 group-has-[:focus-visible]/tooltip:translate-x-0",
	Right:  "left-full top-[round(50%,1px)] ml-[5px] origin-left -translate-y-[round(50%,1px)] -translate-x-2 group-hover/tooltip:translate-x-0 group-has-[:focus-visible]/tooltip:translate-x-0",
}

// The arrow is a rotated square whose centre lies 2px inside the edge of
// the content, as the reference draws it.
var arrowClass = gx.Enum[Side]{
	Top:    "top-full left-[round(50%,1px)] -translate-x-1/2 -translate-y-[calc(50%+2px)]",
	Bottom: "bottom-full left-[round(50%,1px)] -translate-x-1/2 translate-y-[calc(50%+2px)]",
	Left:   "left-full top-[round(50%,1px)] -translate-x-[calc(50%+2px)] -translate-y-1/2",
	Right:  "right-full top-[round(50%,1px)] translate-x-[calc(50%+2px)] -translate-y-1/2",
}

// side returns the side of one tooltip; a zero value is Top.
func (p TooltipProps) side() Side {
	if p.Side == "" {
		return Top
	}
	return p.Side
}

The theme must define these tokens: --foreground, --background.

Usage

<tooltip.Tooltip content="Add to library">
  <button.Button variant={button.Outline}>Add</button.Button>
</tooltip.Tooltip>

The tooltip is CSS only. It shows on hover and on keyboard focus, with an arrow that points at the control. Side selects the edge: tooltip.Top (default), tooltip.Bottom, tooltip.Left or tooltip.Right.

The tooltip fades, zooms and slides in from the control. A user who asks for reduced motion gets no transition.

Examples

Top

<tooltip.Tooltip content="Add to library"><button type="button">Hover me</button></tooltip.Tooltip>
<tooltip.Tooltip content="Add to library" side={tooltip.Right}><button type="button">Hover me</button></tooltip.Tooltip>

Bottom

<tooltip.Tooltip content="Add to library" side={tooltip.Bottom}>
  <button type="button">Hover me</button>
</tooltip.Tooltip>

Left

<tooltip.Tooltip content="Add to library" side={tooltip.Left}><button type="button">Hover me</button></tooltip.Tooltip>

API reference

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

tooltip.Tooltip

Prop Type Default Description
Content string "" Content is the text of the tooltip. An empty value renders no tooltip.
Side Side Top Side sets the edge of the trigger that the tooltip appears on: Top, Bottom, Left or Right.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the element that shows the tooltip on hover or on keyboard focus.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Keep the text to a few words.
  • Wrap a control that needs a name.

Don't

  • Do not put essential information only in a tooltip.
  • Do not put a link inside a tooltip.

Keyboard

Key Action
Tab The tooltip appears when the control takes keyboard focus.