Next.js Tooltip Component with Drivn
Add a tooltip to a Next.js App Router app with Drivn: it needs no 'use client', renders in a Server Component, and positions on all four sides.
A tooltip is a small label that appears when a user hovers or focuses an element — the icon-only button that needs a name, the truncated cell that hides its full value, the setting that wants a one-line hint. Most tooltip libraries reach for a positioning engine and client-side state to do this, which forces the component onto the client. Drivn's Tooltip takes the opposite route: it is pure CSS. The tip shows through Tailwind's group-hover and group-focus-within variants and an opacity transition — no useState, no effect, and no 'use client' directive in the source. That one fact changes how it fits a Next.js App Router app, because a component with no client hooks can render inside a Server Component untouched.
After npx drivn add tooltip the component lives in src/components/ui/tooltip.tsx — a single Tooltip that takes a content string, an optional position, and the trigger as its children. There is no runtime UI dependency and no Radix primitive underneath; the whole file is a styles object and one span wrapping another. This guide installs Drivn in a Next.js 16 project, renders a tooltip inside a Server Component, positions it on any of four sides, and walks the pure-CSS hover-and-focus mechanism so you can restyle it. For the full reference see the Tooltip docs; for the shadcn/ui comparison see Drivn vs shadcn/ui Tooltip.
Install in a Next.js 16 project
Drivn installs through a small CLI that writes the component source straight into your repository — there is no runtime UI package to version-lock and no positioning library underneath. From the root of your Next.js 16 project run npx drivn add tooltip. The command writes a single tooltip.tsx under your UI directory; the CLI reference documents every flag, including targeting a custom path or adding several components at once. The Tooltip has no peer dependencies beyond React itself — it does not even pull a lucide-react icon, since the trigger is whatever children you pass. After install the file is yours: a future Drivn release will not overwrite your edits, and the whole component, from the styles object to the four position classes, is a short file you can read top to bottom. If this is your first Drivn component, the installation guide covers the Tailwind v4 and path-alias setup it assumes.
1 # from the root of your Next.js 16 project 2 npx drivn add tooltip
Render a tooltip without a client boundary
Here is where the Tooltip differs from most interactive components in a Next.js app: it needs no client boundary. Because the tip is shown with CSS group-hover and group-focus-within rather than React state, the source carries no 'use client' directive and no hooks — so you can render a Tooltip directly inside a Server Component, an async layout, or a statically generated page with nothing extra. Wrap any trigger — an icon button, a word, a badge — as the children and pass the label through the content prop. The tip is position="top" by default, sitting above the trigger. There is no provider to mount and no context to thread, unlike a tooltip built on a positioning engine; a single self-contained Tooltip is the whole API. See the Tooltip examples for more triggers and placements.
1 import { Tooltip } from "@/components/ui/tooltip" 2 3 export default function Page() { 4 return ( 5 <Tooltip content="This is a tooltip"> 6 <span>Hover me</span> 7 </Tooltip> 8 ) 9 }
Position the tooltip on four sides
The position prop places the tip on any of four sides of the trigger — top, bottom, left, or right — and defaults to top. Each value maps to an entry in the styles.positions object that combines an absolute-position anchor with a small margin, so top sits the tip above the trigger with bottom-full and an mb-2 gap, while right uses left-full and ml-2. The prop is typed as keyof typeof styles.positions, so your editor autocompletes the four allowed values and rejects anything else — no magic strings. Choose the side by where the trigger sits in the layout: a tooltip on a left-rail icon reads best on the right, while one on a top toolbar button belongs on the bottom. Because the placement is plain CSS offset classes rather than a runtime measurement, there is no flip-on-collision logic — you pick the side that fits.
1 <Tooltip content="Top tooltip" position="top"> 2 <span>Top</span> 3 </Tooltip> 4 5 <Tooltip content="Bottom tooltip" position="bottom"> 6 <span>Bottom</span> 7 </Tooltip> 8 9 <Tooltip content="Left tooltip" position="left"> 10 <span>Left</span> 11 </Tooltip> 12 13 <Tooltip content="Right tooltip" position="right"> 14 <span>Right</span> 15 </Tooltip>
How the hover-and-focus tooltip works
Every behaviour of the tooltip lives in one styles object, and it is worth reading because there is no JavaScript doing the work. The wrapper carries styles.base — relative inline-flex group — which makes it a Tailwind group and the positioning context for the absolutely placed tip. The tip itself is hidden at opacity-0 and revealed by two variants: group-hover:opacity-100 for the mouse and group-focus-within:opacity-100 for the keyboard, with transition-opacity easing between them. It is pointer-events-none so it never intercepts a click, and z-50 so it floats above surrounding content. The surface is bg-foreground text-background — an inverted chip that reads against any background — with rounded-lg, text-xs font-medium, and whitespace-nowrap. The block below is copied verbatim from tooltip.ts; because you own the file, restyling the tip is a direct edit to these classes, not a config override. See theming to align the tokens it references with your app.
1 const styles = { 2 base: 'relative inline-flex group', 3 tip: cn( 4 'absolute z-50 px-2.5 py-1.5 text-xs font-medium', 5 'rounded-lg bg-foreground text-background', 6 'whitespace-nowrap pointer-events-none', 7 'opacity-0 group-hover:opacity-100', 8 'group-focus-within:opacity-100 transition-opacity' 9 ), 10 positions: { 11 top: 'bottom-full left-1/2 -translate-x-1/2 mb-2', 12 bottom: 'top-full left-1/2 -translate-x-1/2 mt-2', 13 left: 'right-full top-1/2 -translate-y-1/2 mr-2', 14 right: 'left-full top-1/2 -translate-y-1/2 ml-2', 15 }, 16 }
Keyboard focus and accessibility
A tooltip that only shows on hover is invisible to keyboard and touch users, so Drivn wires focus in from the start. The wrapper span sets tabIndex={0}, which puts the trigger in the tab order, and group-focus-within:opacity-100 reveals the tip whenever focus lands inside the wrapper — so tabbing to the element shows its label the same way hovering does. The base also sets outline-none on the wrapper, so pair the tooltip with a trigger that carries its own visible focus ring — a Button already does — rather than an unstyled span, so keyboard users can still see where they are. Keep the content short, since the tip is whitespace-nowrap and will not wrap; for a longer hint, a static description near the control reads better than a tooltip. Because the whole thing is a plain focusable span with CSS-driven visibility, it degrades gracefully and adds no client JavaScript to your Next.js bundle.
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
No. The Tooltip is pure CSS — it shows the tip through Tailwind's group-hover and group-focus-within variants and an opacity transition, with no useState, no effect, and no event handlers. Because it has no client hooks, its source carries no 'use client' directive, so you can render it directly inside a Server Component or a statically generated page with nothing extra to configure.
Pass the position prop with top, bottom, left, or right — it defaults to top. Each value maps to a set of absolute-position classes in the styles.positions object, and the prop is typed as keyof typeof styles.positions so your editor autocompletes the four options. There is no collision detection, so choose the side that suits where the trigger sits in your layout.
No. The Tooltip imports only React and the local cn class helper. Placement is done with plain Tailwind offset classes rather than a runtime positioning engine like Floating UI or Popper, and visibility is CSS-driven, so there is no JavaScript measuring the viewport. After npx drivn add tooltip the whole component is a short file of source you own and can edit.
Yes. The wrapper span sets tabIndex={0} so the trigger enters the tab order, and group-focus-within:opacity-100 reveals the tip when focus lands inside it — so keyboard users see the label by tabbing, not only by hovering. Give the trigger a visible focus ring, since the wrapper itself uses outline-none, and keep the content short because the tip does not wrap.

