Tooltip
A short label on hover or focus. Only for extra info; never put required content or actions in a tooltip.
import { Button } from "@/components/ui/button";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@/components/ui/tooltip";
export default function Demo() { return ( <TooltipProvider> <div className="flex gap-2"> {(["top", "right", "bottom", "left"] as const).map((side) => ( <Tooltip key={side}> <TooltipTrigger render={<Button variant="outline" size="sm" />}> {side} </TooltipTrigger> <TooltipContent side={side}>Copy address</TooltipContent> </Tooltip> ))} </div> </TooltipProvider> );}<script setup lang="ts">import { Button } from "@/components/ui/button";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
const sides = ["top", "right", "bottom", "left"] as const;</script>
<template> <TooltipProvider> <div class="flex gap-2"> <Tooltip v-for="side in sides" :key="side"> <TooltipTrigger as-child> <Button variant="outline" size="sm">{{ side }}</Button> </TooltipTrigger> <TooltipContent :side="side">Copy address</TooltipContent> </Tooltip> </div> </TooltipProvider></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import * as Tooltip from "$lib/components/ui/tooltip";
const sides = ["top", "right", "bottom", "left"] as const;</script>
<Tooltip.Provider> <div class="flex gap-2"> {#each sides as side (side)} <Tooltip.Root> <Tooltip.Trigger> {#snippet child({ props })} <Button variant="outline" size="sm" {...props}>{side}</Button> {/snippet} </Tooltip.Trigger> <Tooltip.Content {side}>Copy address</Tooltip.Content> </Tooltip.Root> {/each} </div></Tooltip.Provider>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/tooltippnpm dlx shadcn@latest add @edmi-ui/tooltipyarn dlx shadcn@latest add @edmi-ui/tooltipbunx shadcn@latest add @edmi-ui/tooltipPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/tooltippnpm dlx shadcn-vue@latest add @edmi-ui/tooltipyarn dlx shadcn-vue@latest add @edmi-ui/tooltipbunx shadcn-vue@latest add @edmi-ui/tooltipPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tooltip.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tooltip.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tooltip.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/tooltip.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Copy the code from Component source into your project and update the import paths to match your aliases.
import { Button } from "@/components/ui/button";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@/components/ui/tooltip";
<TooltipProvider> <div className="flex gap-2"> {(["top", "right", "bottom", "left"] as const).map((side) => ( <Tooltip key={side}> <TooltipTrigger render={<Button variant="outline" size="sm" />}> {side} </TooltipTrigger> <TooltipContent side={side}>Copy address</TooltipContent> </Tooltip> ))} </div></TooltipProvider><script setup lang="ts">import { Button } from "@/components/ui/button";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@/components/ui/tooltip";</script>
<template> <TooltipProvider> <div class="flex gap-2"> <Tooltip :key="side"> <TooltipTrigger as-child> <Button variant="outline" size="sm">{{ side }}</Button> </TooltipTrigger> <TooltipContent :side="side">Copy address</TooltipContent> </Tooltip> </div> </TooltipProvider></template><script lang="ts"> import * as Tooltip from "$lib/components/ui/tooltip"; import { Button } from "$lib/components/ui/button";</script>
<Tooltip.Provider> <div class="flex gap-2"> {#each sides as side (side)} <Tooltip.Root> <Tooltip.Trigger> {#snippet child({ props })} <Button variant="outline" size="sm" {...props}>{side}</Button> {/snippet} </Tooltip.Trigger> <Tooltip.Content {side}>Copy address</Tooltip.Content> </Tooltip.Root> {/each} </div></Tooltip.Provider>Component source
Section titled “Component source”import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";import { cn } from "cn";
function TooltipProvider({ delay = 0, ...props}: TooltipPrimitive.Provider.Props) { return ( <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} /> );}
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;}
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) { return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;}
function TooltipContent({ className, side = "top", sideOffset = 6, align = "center", alignOffset = 0, children, ...props}: TooltipPrimitive.Popup.Props & Pick< TooltipPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" >) { return ( <TooltipPrimitive.Portal> <TooltipPrimitive.Positioner align={align} alignOffset={alignOffset} side={side} sideOffset={sideOffset} className="isolate z-50" > <TooltipPrimitive.Popup data-slot="tooltip-content" className={cn( // recipes.tooltip: solid primary, 7px radius. "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-[7px] bg-primary px-2.5 py-1.5 text-[12.5px] text-primary-foreground transition-[opacity,scale] duration-100 has-data-[slot=kbd]:pr-1.5 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 **:data-[slot=kbd]:rounded-sm", className, )} {...props} > {children} <TooltipPrimitive.Arrow className="z-50 size-2 rotate-45 rounded-[1px] bg-primary data-[side=bottom]:-top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-1" /> </TooltipPrimitive.Popup> </TooltipPrimitive.Positioner> </TooltipPrimitive.Portal> );}
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };<script setup lang="ts">import type { TooltipRootEmits, TooltipRootProps } from 'reka-ui'import { TooltipRoot, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<TooltipRootProps>()const emits = defineEmits<TooltipRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)</script>
<template> <TooltipRoot v-slot="slotProps" data-slot="tooltip" v-bind="forwarded" > <slot v-bind="slotProps" /> </TooltipRoot></template><script setup lang="ts">import type { TooltipContentEmits, TooltipContentProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { TooltipArrow, TooltipContent, TooltipPortal, useForwardPropsEmits } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
defineOptions({ inheritAttrs: false,})
const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(), { side: 'top', sideOffset: 6,})const emits = defineEmits<TooltipContentEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <TooltipPortal> <TooltipContent data-slot="tooltip-content" v-bind="{ ...forwarded, ...$attrs }" :class="cn( // recipes.tooltip: solid primary, 7px radius. 'z-50 inline-flex w-fit max-w-xs origin-(--reka-tooltip-content-transform-origin) items-center gap-1.5 rounded-[7px] bg-primary px-2.5 py-1.5 text-[12.5px] text-balance text-primary-foreground has-data-[slot=kbd]:pr-1.5 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:animate-in data-[state=instant-open]:fade-in-0 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:rounded-sm', props.class, )" > <slot /> <TooltipArrow class="z-50 size-2 translate-y-[calc(-50%-1px)] rotate-45 rounded-[1px] bg-primary fill-primary" /> </TooltipContent> </TooltipPortal></template><script setup lang="ts">import type { TooltipProviderProps } from 'reka-ui'import { TooltipProvider } from 'reka-ui'
const props = withDefaults(defineProps<TooltipProviderProps>(), { delayDuration: 0,})</script>
<template> <TooltipProvider v-bind="props"> <slot /> </TooltipProvider></template><script setup lang="ts">import type { TooltipTriggerProps } from 'reka-ui'import { TooltipTrigger } from 'reka-ui'
const props = defineProps<TooltipTriggerProps>()</script>
<template> <TooltipTrigger data-slot="tooltip-trigger" v-bind="props" > <slot /> </TooltipTrigger></template>export { default as Tooltip } from "./Tooltip.vue";export { default as TooltipContent } from "./TooltipContent.vue";export { default as TooltipProvider } from "./TooltipProvider.vue";export { default as TooltipTrigger } from "./TooltipTrigger.vue";<script lang="ts"> import { Tooltip as TooltipPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import type { WithoutChildrenOrChild } from "$lib/utils.js"; import TooltipPortal from "./tooltip-portal.svelte"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, sideOffset = 6, side = "top", children, arrowClasses, portalProps, ...restProps }: TooltipPrimitive.ContentProps & { arrowClasses?: string; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof TooltipPortal>>; } = $props();</script>
<TooltipPortal {...portalProps}> <TooltipPrimitive.Content bind:ref data-slot="tooltip-content" {sideOffset} {side} class={cn( // recipes.tooltip: solid primary, 7px radius. "z-50 inline-flex w-fit max-w-xs origin-(--bits-tooltip-content-transform-origin) items-center gap-1.5 rounded-[7px] bg-primary px-2.5 py-1.5 text-[12.5px] text-balance text-primary-foreground has-data-[slot=kbd]:pr-1.5 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:animate-in data-[state=instant-open]:fade-in-0 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:rounded-sm", className )} {...restProps} > {@render children?.()} <TooltipPrimitive.Arrow> {#snippet child({ props })} <div class={cn( "z-50 size-2 translate-y-[calc(-50%-2px)] rotate-45 rounded-[1px] bg-primary fill-primary data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%+1px)] data-[side=left]:-translate-y-[calc(50%-3px)] data-[side=right]:translate-x-[calc(50%+2px)] data-[side=right]:translate-y-1/2 data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%+2px)]", arrowClasses )} {...props} ></div> {/snippet} </TooltipPrimitive.Arrow> </TooltipPrimitive.Content></TooltipPortal><script lang="ts"> import { Tooltip as Primitive } from "bits-ui";
let { ...restProps }: Primitive.PortalProps = $props();</script>
<Primitive.Portal {...restProps} /><script lang="ts"> import { Tooltip as TooltipPrimitive } from "bits-ui";
let { delayDuration = 0, ...restProps }: TooltipPrimitive.ProviderProps = $props();</script>
<TooltipPrimitive.Provider {delayDuration} {...restProps} /><script lang="ts" generics="T = never"> import { Tooltip as TooltipPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps<T> = $props();</script>
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} /><script lang="ts" generics="T = never"> import { Tooltip as TooltipPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: TooltipPrimitive.RootProps<T> = $props();</script>
<TooltipPrimitive.Root bind:open {...restProps} />import Root from "./tooltip.svelte";import Content from "./tooltip-content.svelte";import Portal from "./tooltip-portal.svelte";import Provider from "./tooltip-provider.svelte";import Trigger from "./tooltip-trigger.svelte";
export { Content, Content as TooltipContent, Portal, Portal as TooltipPortal, Provider, Provider as TooltipProvider, Root, // Root as Tooltip, Trigger, Trigger as TooltipTrigger,};| Prop | Type | Default |
|---|---|---|
TooltipContent side |
top | right | bottom | left |
top |
TooltipContent sideOffset |
number |
6 |
TooltipProvider delay |
number (ms) |
0 |
Parts: TooltipProvider, Tooltip, TooltipTrigger, TooltipContent. A disabled trigger must be wrapped in a span.
✦ Edmi additions
Section titled “✦ Edmi additions”- Solid
--primarychip (7px radius) with a small arrow; aKbdinside gets tighter right padding.