Tooltip
A bubble with a caret, anchored to a target — simple, standard, or rich with a title, media, steps and a close button.
TooltipOpen in Figma ↗Use for
Contextual popover that surfaces supplementary information anchored to a trigger element — an icon button, a label, or an inline element. Use when the information is helpful but not critical enough to be shown inline, and when the user needs to actively request it. The caret points toward the trigger to maintain spatial context.
Not for
Persistent information that the user needs to complete a task — surface that inline or in a form hint. Error messages or validation — use an inline error state instead. Any content that requires user input or a form — tooltips are read-only. Navigation or dismissal flows.
Choosing
size=simple — A single line of body text with no container border-radius or padding beyond 4px vertical / 12px horizontal. Use for very short labels or values where a full card would feel heavy. Width hugs the content.
size=standard — A multi-line body text block inside a card with border-radius/3 (12px) and 12px padding. Use for short explanatory text up to 4 lines. Width is between 200px and 280px. Use when a label alone isn't enough but no image or CTA is needed.
size=rich — A full card with border-radius/6 (20px) and 16px padding, supporting a title, optional media image, body text, optional icon button in the header, and an optional action block (step counter + CTA button). Width is fixed at 280px. Use for onboarding tours, feature highlights, or any tooltip that needs structured content with a call to action.
For caret-direction: set bottom to position the tooltip above the trigger (caret points down), top to position it below (caret points up), left to position it to the right of the trigger (caret points left), right to position it to the left (caret points right). Match the caret direction to the actual spatial relationship between the tooltip and its trigger — do not choose direction for aesthetic reasons.
For caret-placement on simple and standard: set left, center, or right to align the caret to the appropriate edge of the tooltip relative to where the trigger sits horizontally.
For surface=default use the white canvas surface on light backgrounds. For surface=dark use the dark surface variant on dark or midnight backgrounds.
For the rich tooltip: set mediaBlock=true when a supporting image adds meaningful context; set mediaBlock=false when text alone is sufficient. Set iconButton=true when there is a dismiss or secondary action needed in the header. Set actionBlock=true when the tooltip is part of a multi-step tour and needs a step counter or CTA; set actionBlock=false for a single informational tooltip with no required action. Set cta=true inside the action block when a primary button is needed; set cta=false when only the step counter is needed. The step counter uses body/sm (12px regular) in muted color (color/text/default/muted) and shows the format "1 of 3".
Set direction=rtl for Arabic on standard and rich variants — body text uses dir=auto and flips automatically. For RTL, the step counter and button order in the action block reverse.
Rules
All three sizes apply shadow/md (color/midnight/a10, 22px blur). Do not remove the shadow or add additional elevation. The caret is a 6px tall SVG asset — do not resize it or replace it with a custom shape. The caret always fills the full width of the tooltip container. On simple tooltips, the body text uses body/sm (12px regular) in subtle color and never wraps. On standard tooltips, the body text uses body/sm in default dark color and wraps up to 4 lines. On rich tooltips, the title uses title/xs (14px semi-bold) and the body uses body/sm (12px regular) in subtle color. The media image is fixed at 140px height with border-radius/6 (20px) and object-cover. The icon button in the rich header is 32px with border-radius/3 (12px). The CTA button uses size=md (40px), primary style, scheme=default. Do not add more than one button to the action block.
import SwiftUI import EandTokens // Tooltip standard: 200 wide, r12, padding 12, white (surface/canvas/default), shadow/md, // body/sm default ink, and a 12×6 caret facing the trigger (inset 32 when not centred). // simple = r4, padding 4/12; rich = r20, padding 16 with title, media, body and a close. // surface dark = surface/canvas/midnight with inverse ink. struct Tooltip: View { let content: String var body: some View { VStack(spacing: 0) { Text(content) .font(.system(size: EandTypography.bodySm.size)) .foregroundColor(EandColor.textDefaultDefault) .padding(EandSpacing.md) .frame(width: 200, alignment: .leading) .background(EandColor.surfaceCanvasDefault) .clipShape(RoundedRectangle(cornerRadius: EandRadius.radius4)) Caret().fill(EandColor.surfaceRaisedDefault) .frame(width: EandScale.scale12, height: 6) // placement=top: caret below } .shadow(color: EandColor.midnightA10, radius: 22) // shadow/md: blur 44 } } struct Caret: Shape { func path(in r: CGRect) -> Path { Path { p in p.move(to: .zero); p.addLine(to: CGPoint(x: r.maxX, y: 0)); p.addLine(to: CGPoint(x: r.midX, y: r.maxY)) } } }
When to use
- Use to explain a control or a term in place, or for a guided hint during onboarding (the
rich size, with
steps). - Don't use for information the user needs to complete the task; put it on the page. Not for status (use Alert) or for long content (use BottomSheet).
Choosing a size
size | Width | Content |
|---|---|---|
simple | Hugs one line | content only |
standard (default) | 200px | content as a short paragraph |
rich | 280px | title, media, content, then steps and an action; onClose adds a close button |
surface is default (white) or dark (midnight).
Placement and caret
placementis the side of the target the bubble sits on:top(default),bottom,leftorright. The caret always points back at the target.caretAlignmoves the caret along the edge:start,center(default) orend, inset 32px from the corner. It applies totopandbottom; on the sides the caret stays centred.caret={false}hides it.
Visibility is controlled: the tooltip shows while visible is true. Wire it to your own hover,
focus or tap handling.
<Tooltip
size="rich"
visible={open}
placement="bottom"
caretAlign="start"
title="Track your usage"
content="See what is left of your data, minutes and roaming."
steps="1 of 3"
action={<Button size="sm">Next</Button>}
onClose={() => setOpen(false)}
>
<Icon name="info" size={20} />
</Tooltip>Props
| Prop | Type | Default | Description |
|---|---|---|---|
content | ReactNode | — | |
action? | ReactNode | — | Rich only — the action block's button. |
caret? | boolean | true | Figma show-caret. Default true. |
caretAlign? | 'end' | 'start' | 'center' | center | |
media? | ReactNode | — | Rich only — .tooltip-media-image, clipped to border-radius/7. |
onClose? | (() => void) | — | Rich only — shows the .tooltip-icon-button close at the top end. |
placement? | 'left' | 'top' | 'bottom' | 'right' | top | |
size? | 'simple' | 'standard' | 'rich' | standard | |
steps? | ReactNode | — | Rich only — .tooltip-steps, e.g. "1 of 3". |
surface? | 'default' | 'dark' | default | |
title? | ReactNode | — | Rich only — .tooltip-text-title. |
visible? | boolean | false |
Design
Figma source: Tooltip — etisalat Consumer App DSL V1.2
- Simple: 4px radius (
border-radius/1), padding 4/12. Standard: 12px radius (border-radius/4), padding 12. Rich: 20px radius (border-radius/7), padding 16. - Surface
surface/canvas/defaultorsurface/canvas/midnight; every size carriesshadow/md(0 0 44px,midnight/a10). - Caret: a 12 × 6 shape on
surface/raised/default(orsurface/canvas/midnightwhen dark), inset byspacing/3xlwhen it is not centred. - Rich: the media clips to a 20px radius; the body is
body/smintext/default/subtle; the close is a 32px button at a 12px radius onsurface/base/default, holding a 17pxclose-filled.