diff --git a/react/src/components/COMPONENTS.md b/react/src/components/COMPONENTS.md
index c17cbbe..b01ece7 100644
--- a/react/src/components/COMPONENTS.md
+++ b/react/src/components/COMPONENTS.md
@@ -44,3 +44,21 @@ provided the badge becomes a keyboard-focusable interactive control
Verified
alert('clicked')}>Dismiss
```
+
+## Tooltip
+
+Shows contextual text on hover and keyboard focus, dismissible with Escape.
+Renders `role="tooltip"` and links the trigger via `aria-describedby`. Wraps a
+single focusable element.
+
+| Prop | Type | Default | Description |
+| ----------- | ----------------------------------------- | ------- | ----------------------------- |
+| `content` | `React.ReactNode` | (none) | Tooltip contents. |
+| `children` | `React.ReactElement` | (none) | The focusable trigger. |
+| `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Position relative to trigger. |
+
+```tsx
+
+
+
+```
diff --git a/react/src/components/Tooltip.test.tsx b/react/src/components/Tooltip.test.tsx
new file mode 100644
index 0000000..c9aeb21
--- /dev/null
+++ b/react/src/components/Tooltip.test.tsx
@@ -0,0 +1,41 @@
+import '@testing-library/jest-dom';
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+
+import { Tooltip } from './Tooltip';
+
+describe('Tooltip', () => {
+ it('is hidden until hovered, then shows with role=tooltip and links the trigger', () => {
+ const { container } = render(
+
+
+ ,
+ );
+ const wrapper = container.firstChild as HTMLElement;
+ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
+
+ fireEvent.mouseEnter(wrapper);
+ const tip = screen.getByRole('tooltip');
+ expect(tip).toHaveTextContent('Help text');
+ expect(screen.getByRole('button', { name: 'trigger' })).toHaveAttribute(
+ 'aria-describedby',
+ tip.id,
+ );
+
+ fireEvent.mouseLeave(wrapper);
+ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
+ });
+
+ it('dismisses on Escape', () => {
+ const { container } = render(
+
+
+ ,
+ );
+ const wrapper = container.firstChild as HTMLElement;
+ fireEvent.mouseEnter(wrapper);
+ expect(screen.getByRole('tooltip')).toBeInTheDocument();
+ fireEvent.keyDown(wrapper, { key: 'Escape' });
+ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
+ });
+});
diff --git a/react/src/components/Tooltip.tsx b/react/src/components/Tooltip.tsx
new file mode 100644
index 0000000..df27416
--- /dev/null
+++ b/react/src/components/Tooltip.tsx
@@ -0,0 +1,54 @@
+import React, { cloneElement, useId, useState } from 'react';
+
+export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
+
+export interface TooltipProps {
+ content: React.ReactNode;
+ children: React.ReactElement;
+ placement?: TooltipPlacement;
+}
+
+const PLACEMENT_STYLES: Record = {
+ top: { bottom: '100%', left: '50%', transform: 'translateX(-50%)', marginBottom: 6 },
+ bottom: { top: '100%', left: '50%', transform: 'translateX(-50%)', marginTop: 6 },
+ left: { right: '100%', top: '50%', transform: 'translateY(-50%)', marginRight: 6 },
+ right: { left: '100%', top: '50%', transform: 'translateY(-50%)', marginLeft: 6 },
+};
+
+const tooltipStyle: React.CSSProperties = {
+ position: 'absolute',
+ zIndex: 50,
+ whiteSpace: 'nowrap',
+ borderRadius: 6,
+ background: '#111827',
+ color: '#ffffff',
+ fontSize: 12,
+ padding: '4px 8px',
+ pointerEvents: 'none',
+};
+
+/** Accessible tooltip shown on hover and keyboard focus, dismissible with Escape. */
+export function Tooltip({ content, children, placement = 'top' }: TooltipProps) {
+ const [open, setOpen] = useState(false);
+ const id = useId();
+
+ return (
+ setOpen(true)}
+ onMouseLeave={() => setOpen(false)}
+ onFocus={() => setOpen(true)}
+ onBlur={() => setOpen(false)}
+ onKeyDown={(e) => {
+ if (e.key === 'Escape') setOpen(false);
+ }}
+ >
+ {cloneElement(children, { 'aria-describedby': open ? id : undefined })}
+ {open ? (
+
+ {content}
+
+ ) : null}
+
+ );
+}
diff --git a/react/src/components/index.ts b/react/src/components/index.ts
index aa7c45f..256b1bb 100644
--- a/react/src/components/index.ts
+++ b/react/src/components/index.ts
@@ -3,3 +3,6 @@ export type { AlertProps, AlertVariant } from './Alert';
export { Badge } from './Badge';
export type { BadgeProps, BadgeVariant, BadgeSize } from './Badge';
+
+export { Tooltip } from './Tooltip';
+export type { TooltipProps, TooltipPlacement } from './Tooltip';