Tooltip
A small contextual hint that works with hover, focus, and Escape dismissal.
Live preview
Adjust a setting to update the preview and usage code together.
Usage & source
"use client";
import type { CSSProperties } from "react";
import { Tooltip } from "./components/stepkit/Tooltip";
import "./components/stepkit/styles.css";
const themeStyle = {"--stepkit-bg":"#f7f7f8","--stepkit-surface":"#ffffff","--stepkit-surface-muted":"#f0f0f3","--stepkit-fg":"#17181c","--stepkit-muted":"#626572","--stepkit-border":"#e3e4e9","--stepkit-border-strong":"#c8cad3","--stepkit-code":"#f1f1f4","--stepkit-shadow":"0 20px 60px rgba(31, 26, 64, .10)","--stepkit-accent":"#6950df","--stepkit-accent-strong":"#5137c7","--stepkit-accent-soft":"#eeeafd","--stepkit-accent-on-soft":"#5137c7","--stepkit-accent-text":"#ffffff","--stepkit-error":"#b43145","--stepkit-placeholder":"#626572","--stepkit-radius":"16px","--stepkit-space":"16px","--stepkit-control-height":"44px","--stepkit-font-sans":"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"} as CSSProperties;
export default function Example() {
return (
<div className="stepkit-theme" data-stepkit-preset="modern" style={themeStyle}>
<Tooltip
content={"Copied to clipboard"}
><button className="stepkit stepkit-button" type="button">Hover or focus</button></Tooltip>
</div>
);
}Dependencies
[email protected] [email protected]
/*
* Stepkit 0.2.0
* Copyright (c) 2026 Vladislav Stepanov
*
* Original Stepkit portions in this release are licensed under the MIT License. Adapted portions identify their upstream licence and copyright in the copied source and in `NOTICE-MAGICUI.md` where applicable.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
"use client";
import { cloneElement, isValidElement, useId, useState, type FocusEvent, type KeyboardEvent, type ReactElement, type ReactNode } from "react";
import "./styles.css";
interface TriggerProps { "aria-describedby"?: string; onFocus?: (event: FocusEvent<HTMLElement>) => void; onBlur?: (event: FocusEvent<HTMLElement>) => void; onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void; }
export interface TooltipProps { content: string; children: ReactNode; defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; }
export function Tooltip({ content, children, defaultOpen = false, open: controlledOpen, onOpenChange }: TooltipProps) {
const id = useId(); const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen); const isControlled = controlledOpen !== undefined; const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
const setOpen = (next: boolean) => { if (!isControlled) setUncontrolledOpen(next); onOpenChange?.(next); };
const child = isValidElement(children) ? children as ReactElement<TriggerProps> : null;
const childProps = child?.props;
const describedBy = [childProps?.["aria-describedby"], isOpen ? id : null].filter(Boolean).join(" ") || undefined;
const onFocus = (event: FocusEvent<HTMLElement>) => { childProps?.onFocus?.(event); if (!event.defaultPrevented) setOpen(true); };
const onBlur = (event: FocusEvent<HTMLElement>) => { childProps?.onBlur?.(event); if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false); };
const onKeyDown = (event: KeyboardEvent<HTMLElement>) => { childProps?.onKeyDown?.(event); if (event.key === "Escape" && !event.defaultPrevented) { event.preventDefault(); setOpen(false); } };
const triggerProps: TriggerProps = { "aria-describedby": describedBy, onFocus, onBlur, onKeyDown };
const trigger = child ? cloneElement(child, triggerProps) : <span tabIndex={0} {...triggerProps}>{children}</span>;
return <span className="stepkit stepkit-tooltip" data-open={isOpen} onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}><span>{trigger}</span><span className="stepkit-tooltip__content" id={id} role="tooltip">{content}</span></span>;
}
export default Tooltip;
components/Tooltip.tsxcomponents/styles.cssInstall
Use the released registry entry or download the complete archive.
npx [email protected] add https://vstepanov.com/stepkit/r/tooltip.jsonIntegration prompt
Install Stepkit Tooltip 0.2.0 from the registry and preserve the included MIT notice. npx [email protected] add https://vstepanov.com/stepkit/v0.2.0/r/tooltip.json Install the tested runtime dependencies exactly: npm install [email protected] [email protected] Keep the component source, shared styles, NOTICE.md, and provenance files together. Apply the theme tokens on a wrapper rather than passing theme keys as component props. Usage: ```tsx "use client"; import type { CSSProperties } from "react"; import { Tooltip } from "./components/stepkit/Tooltip"; import "./components/stepkit/styles.css"; const themeStyle = {"--stepkit-bg":"#f7f7f8","--stepkit-surface":"#ffffff","--stepkit-surface-muted":"#f0f0f3","--stepkit-fg":"#17181c","--stepkit-muted":"#626572","--stepkit-border":"#e3e4e9","--stepkit-border-strong":"#c8cad3","--stepkit-code":"#f1f1f4","--stepkit-shadow":"0 20px 60px rgba(31, 26, 64, .10)","--stepkit-accent":"#6950df","--stepkit-accent-strong":"#5137c7","--stepkit-accent-soft":"#eeeafd","--stepkit-accent-on-soft":"#5137c7","--stepkit-accent-text":"#ffffff","--stepkit-error":"#b43145","--stepkit-placeholder":"#626572","--stepkit-radius":"16px","--stepkit-space":"16px","--stepkit-control-height":"44px","--stepkit-font-sans":"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"} as CSSProperties; export default function Example() { return ( <div className="stepkit-theme" data-stepkit-preset="modern" style={themeStyle}> <Tooltip content={"Copied to clipboard"} ><button className="stepkit stepkit-button" type="button">Hover or focus</button></Tooltip> </div> ); } ``` Selected bounded settings: {"content":"Copied to clipboard"}. Selected theme: {"mode":"light","accent":"violet","radius":"soft","density":"comfortable","preset":"modern"}.
Props
| Name | Type | Default | Description |
|---|---|---|---|
content | string | "Copied to clipboard" | Tooltip text. |
children | ReactElement | ReactNode | required | The trigger element or text. |
open | boolean | undefined | Optional controlled visibility. |
Accessibility & limits
- Cloned element triggers receive aria-describedby while open.
- Keyboard focus opens the hint and Escape dismisses it.
Limitations
- Keep tooltip content short and supplementary.
Provenance
Original source under the MIT licence.
Original Stepkit implementation.
Required notices: NOTICE.md, PROVENANCE-stepkit-tooltip.md
- 2 source files in the release
- Version 0.2.0