Skip to content

Tooltip

A small floating label that appears above an element on hover or focus.

A hover bubble. It is a hint, never the only place information lives — a control whose meaning is only in its tooltip is a control with no accessible name.

Root class newt-tooltip
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/registry/bases/newt/ui/tooltip"

export default function TooltipDemo() {
  return (
    <Tooltip>
      <TooltipTrigger>
        <button
          type="button"
          aria-describedby="tooltip-example"
          className="inline-flex h-8 w-8 items-center justify-center rounded-full bg-newt-bg-elevated text-sm font-medium text-newt-text-secondary transition-colors duration-fast ease-newt hover:bg-newt-bg-hover hover:text-newt-text-primary"
        >
          ?
        </button>
      </TooltipTrigger>
      <TooltipContent id="tooltip-example">Tooltip text</TooltipContent>
    </Tooltip>
  )
}
<script setup lang="ts">
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "~~/registry/bases/newt/ui/tooltip"
</script>

<template>
  <Tooltip>
    <TooltipTrigger>
      <button
        type="button"
        aria-describedby="tooltip-example"
        class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-newt-bg-elevated text-sm font-medium text-newt-text-secondary transition-colors duration-fast ease-newt hover:bg-newt-bg-hover hover:text-newt-text-primary"
      >
        ?
      </button>
    </TooltipTrigger>
    <TooltipContent id="tooltip-example">Tooltip text</TooltipContent>
  </Tooltip>
</template>

Installation

pnpm dlx newtui@latest add tooltip
npx newtui@latest add tooltip
yarn dlx newtui@latest add tooltip
bunx newtui@latest add tooltip

Copy the source into your project

import * as React from "react"

import { cn } from "@/lib/utils"

const Tooltip = React.forwardRef<
  HTMLSpanElement,
  React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
  <span
    ref={ref}
    className={cn("group relative inline-flex", className)}
    {...props}
  />
))
Tooltip.displayName = "Tooltip"

const TooltipTrigger = React.forwardRef<
  HTMLSpanElement,
  React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
  <span ref={ref} className={cn("inline-flex", className)} {...props} />
))
TooltipTrigger.displayName = "TooltipTrigger"

const TooltipContent = React.forwardRef<
  HTMLSpanElement,
  React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
  <span
    ref={ref}
    role="tooltip"
    className={cn(
      "pointer-events-none absolute bottom-[calc(100%+8px)] start-1/2 -translate-x-1/2 whitespace-nowrap rounded-sm bg-[#111214] px-2.5 py-1.5 text-xs font-medium text-newt-text-primary opacity-0 shadow-elevation-high transition-opacity duration-fast ease-newt group-hover:opacity-100 group-focus-within:opacity-100",
      className
    )}
    {...props}
  />
))
TooltipContent.displayName = "TooltipContent"

export { Tooltip, TooltipTrigger, TooltipContent }
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <span :class="cn('group relative inline-flex', props.class)">
    <slot />
  </span>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <span :class="cn('inline-flex', props.class)">
    <slot />
  </span>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <span
    role="tooltip"
    :class="
      cn(
        'pointer-events-none absolute bottom-[calc(100%+8px)] start-1/2 -translate-x-1/2 whitespace-nowrap rounded-sm bg-[#111214] px-2.5 py-1.5 text-xs font-medium text-newt-text-primary opacity-0 shadow-elevation-high transition-opacity duration-fast ease-newt group-hover:opacity-100 group-focus-within:opacity-100',
        props.class
      )
    "
  >
    <slot />
  </span>
</template>
export { default as Tooltip } from "./Tooltip.vue"
export { default as TooltipTrigger } from "./TooltipTrigger.vue"
export { default as TooltipContent } from "./TooltipContent.vue"

Update the import paths

The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.

Usage

import { Tooltip } from "@/components/ui/tooltip"

export function Example() {
  return <Tooltip />
}
<script setup lang="ts">
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"
</script>

<template>
  <Tooltip />
</template>

API reference

Tooltip

PropType
content?ReactNode

What the bubble says.

TypeReactNode
side?"top" | "right" | "bottom" | "left"

Preferred side. Flips when there is no room.

Type"top" | "right" | "bottom" | "left"
Default"top"

Every part also accepts the native attributes of the element it renders, and a className that is merged rather than replaced.

Accessibility

The bubble is linked to its trigger with aria-describedby, so it is read as a description rather than as the name. It opens on focus as well as hover, and Escape dismisses it. Do not put a control inside a tooltip: it cannot be reached by keyboard.

Tokens

Tooltip reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-tooltip.