Info tip
The i-button popover that explains jargon in place, with an optional help video.
Payment approvals
Who signs off on a payment run before money leaves Cedarline's accounts.
import { InfoTip } from "@oration/canon/components/info-tip";import { Input } from "@oration/canon/components/input";import { SettingsGroup, SettingsRow, SettingsSection } from "@oration/canon/components/settings-section";import { Switch } from "@oration/canon/components/switch";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function Hero() { const [positivePay, setPositivePay] = React.useState(true); const [threshold, setThreshold] = React.useState("1,000,000"); return ( <div className="w-full max-w-2xl text-left"> <SettingsSection title="Payment approvals" description="Who signs off on a payment run before money leaves Cedarline's accounts." info={ <InfoTip title="Payment approvals" description="Runs above the threshold wait for an approver before they're sent to the bank. Smaller runs go out on schedule." video={{ youtubeId: "aircAruvnKk", title: "Set up payment approvals", duration: "3:12", }} learnMoreHref="/design/patterns/education" /> } > <SettingsGroup> <SettingsRow label="Approval threshold" description="Runs above this amount need Maya Okafor or another approver." htmlFor="approval-threshold" > <div className="relative w-40"> <span className="pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-13 text-muted-foreground"> $ </span> <Input id="approval-threshold" inputMode="numeric" value={threshold} onChange={(event) => setThreshold(event.target.value) } aria-describedby="approval-threshold-description" className="pl-6 text-right tabular-nums" /> </div> </SettingsRow> <SettingsRow label="Positive pay" description="Send the bank a list of every check before it's mailed." htmlFor="positive-pay" inline info={ <InfoTip title="Positive pay" description="Your bank only clears checks that match the file Cedarline sends it, by number and amount. Altered or forged checks are rejected." /> } > <Switch id="positive-pay" checked={positivePay} onCheckedChange={(checked) => { setPositivePay(checked); toast.add({ title: checked ? "Positive pay is on" : "Positive pay is off", description: checked ? "The next check file goes to the bank on Friday." : "Checks clear without a match file.", }); }} /> </SettingsRow> </SettingsGroup> </SettingsSection> </div> );}Usage#
Info tip is the small (i) beside a section title, metric or piece of jargon. It opens a 20rem popover with a title, one or two sentences, and optionally a help video and a link to the full article, so a page can explain Days payable outstanding or Remittance advice without a tour or a wall of help text. It is how Oration teaches in place, and it is used on nearly every settings section and metric. The common mistake is hiding required information in it: an info tip is for the curious, so the page must still work for someone who never opens one.
When to use
- Beside a settings section title or row label whose name is jargon: Approval threshold, Positive pay, Warm transfer.
- Beside a metric in a stat strip or chart header: Days payable outstanding, Match rate.
- When a short explainer, a two-minute video or a help article would stop someone guessing.
- On dense tool screens where a page intro would push the tool down.
When not to use
- To name an icon button or show a shortcut. Use Tooltip
- To explain what a whole overview page is for, once, at the top. Use Page intro
- For instructions someone needs to fill a field in correctly. Put them under the field. Use Field
- For a warning or a state that needs attention. Use Alert
- For a small form or actions anchored to a button. Use Popover
Pages explain themselves
The Quiet Indigo Rule
Anatomy#
Days payable outstanding
The average number of days between receiving an invoice and paying it.
Learn more- Trigger. A 16px round button with a 14px info icon in Slate Meta, named About {title}. Its hit area extends to 24px.
- Title. 14px semibold, balanced, naming the term.
- Description. 13px on a 20px line, one or two sentences in plain words. Accepts rich content.
- Video. Optional Video facade: a thumbnail with a duration that loads the player only on play.
- Learn more. Optional 13px indigo link. External links open in a new tab with an arrow icon.
Examples#
Basic
A title and one or two sentences, 6px after the term it explains. Hover for 350ms or click the (i).
import { InfoTip } from "@oration/canon/components/info-tip";export function Basic() { return ( <span className="flex items-center gap-1.5 text-sm font-medium"> Remittance advice <InfoTip title="Remittance advice" description="The note sent with a payment that lists which invoices it covers, so Northwind Freight can match the money to its records." /> </span> );}Learn more link
learnMoreHref adds a link to the full article. Internal paths open in place; http(s):// links open in a new tab with an arrow and say so to screen readers.
import { InfoTip } from "@oration/canon/components/info-tip";export function LearnMore() { return ( <div className="flex flex-col items-start gap-4"> <span className="flex items-center gap-1.5 text-sm font-medium"> Early payment discount <InfoTip title="Early payment discount" description="Terms such as 2/10 net 30 take 2% off when you pay within 10 days. Cedarline schedules eligible invoices early when cash allows." learnMoreHref="/design/patterns/education" /> </span> <span className="flex items-center gap-1.5 text-sm font-medium"> Form W-9 <InfoTip title="Form W-9" description="The IRS form a US supplier fills in so you can report what you paid them. Cedarline holds payments to suppliers without one on file." learnMoreHref="https://www.irs.gov/forms-pubs/about-form-w-9" /> </span> </div> );}With a help video
video adds a Video facade: only the thumbnail loads until someone presses play, then the privacy-enhanced player takes its place and takes focus.
import { InfoTip } from "@oration/canon/components/info-tip";export function WithVideo() { return ( <span className="flex items-center gap-1.5 text-sm font-medium"> Remittance matching <InfoTip title="Remittance matching" description="Cedarline reads each remittance and pairs its lines with open invoices, so short pays and duplicates show up before month end." video={{ youtubeId: "zjkBMFhNj_g", title: "Match remittances to invoices", duration: "4:05", }} learnMoreHref="/design/patterns/education" /> </span> );}Beside a metric
Stat, Settings section and Settings row take an info slot for exactly this. Open metric tips below the strip with side="bottom" so they don't cover the header.
- Days payable outstanding
- 38Down 4 days vs August
- Match rate
- 96.4%
- Paid this month
- $4.2M
import { InfoTip } from "@oration/canon/components/info-tip";import { Stat, StatStrip } from "@oration/canon/components/stat";export function BesideMetric() { return ( <StatStrip className="w-full max-w-2xl"> <Stat label="Days payable outstanding" value="38" delta={{ value: "4 days", direction: "down", label: "vs August", }} info={ <InfoTip title="Days payable outstanding" description="The average number of days between receiving an invoice and paying it. Longer holds cash; too long strains suppliers." side="bottom" /> } /> <Stat label="Match rate" value="96.4%" info={ <InfoTip title="Match rate" description="The share of remittance lines Cedarline matched to an invoice without a person stepping in." side="bottom" /> } /> <Stat label="Paid this month" value="$4.2M" /> </StatStrip> );}States#
| State | Treatment |
|---|---|
| Rest | The (i) in Slate Meta with a help cursor. |
| Hover | The icon turns ink over 150ms. After 350ms the popover opens. |
| Focus visible | The global 2px Focus Indigo outline at a 2px offset. |
| Open | The icon stays ink (data-popup-open). The popover grows from a 0.97 scale over 160ms. |
| Closing | 150ms after the pointer leaves the trigger and the popover, it fades out over 110ms. |
| Video playing | The thumbnail is replaced by the privacy-enhanced YouTube player, which takes focus. |
Behavior#
- Click opens it, and so does hovering the (i) for 350ms with a pointer. Touch and keyboard users open it by tapping or pressing Enter.
- It stays open while the pointer is on the trigger or the popover, and closes 150ms after it leaves both, so there's time to cross to the play button or the link.
- Opened by click or keyboard, focus moves to the first control inside: the video's play button or the link. Escape or a click outside closes it and returns focus to the (i).
- The popover is 20rem wide, capped at the viewport minus 2rem, 4px from the trigger, and flips when it would leave the screen.
sidedefaults to top andalignto center. - The video costs nothing until played: only the thumbnail loads, and the youtube-nocookie player loads on click with autoplay.
learnMoreHrefstarting withhttp://orhttps://opens in a new tab withrel="noopener noreferrer", an arrow icon and (opens in a new tab) for screen readers. Anything else navigates in place.- It uses Popover's motion: 160ms in from a 0.97 scale and 110ms out, reduced to a fade under reduced motion.
Do and don't#
learnMoreHref.Content#
- The title is the term itself, in sentence case: Days payable outstanding, not What is DPO?
- The description starts with what it is, then why it matters: The average number of days between receiving an invoice and paying it. Longer holds cash; too long strains suppliers.
- Use numbers and Cedarline's own objects in examples: suppliers, invoices, payment runs.
- Video titles read like Oration help videos, with a real duration: Match remittances to invoices, 3:12.
Accessibility#
- The trigger is a button named About {title}, so a list of buttons reads About Approval threshold, not Info.
- The popover is labelled by its title and described by its description.
- The link reads Learn more about {title}, plus (opens in a new tab) when external, so several on one page stay distinct.
- The play button is named Play video: {title}, 3 minutes 12 seconds.
- The visible icon is 16px; a pseudo-element extends the hit area to 24px. On touch layouts consider spacing it further from other targets.
- Hover opening is a convenience. Everything is reachable by click, tap and keyboard.
| Keys | Action |
|---|---|
| Tab | Focuses the (i), then the controls inside when open. |
| Enter | Opens or closes the tip. |
| Space | Opens or closes the tip. |
| Esc | Closes the tip and returns focus to the (i). |
Design tokens#
| Token | Used for |
|---|---|
--muted-foreground | The (i) at rest |
--foreground | The (i) on hover and open; title |
--popover | The popover surface |
shadow-md | The overlay shadow, with a 1px ink ring at 10% |
--primary | The Learn more link |
--muted | The video facade before its thumbnail loads |
--radius-lg | 10px popover corners |
API reference#
InfoTip
The trigger and its popover. Built on Popover and Video facade.
Other props spread onto Nothing. Only the props below are accepted..
| Prop | Type | Default | Description |
|---|---|---|---|
titleRequired | string | No default | The term. Also names the trigger (About {title}) and the link. |
descriptionRequired | ReactNode | No default | One or two sentences. Rendered in a div, so it can hold rich content. |
video | { youtubeId: string; title: string; duration: string } | No default | A help video shown as a click-to-load facade. |
learnMoreHref | string | No default | Adds a Learn more link. http(s):// opens in a new tab. |
side | "top" | "bottom" | "left" | "right" | "top" | Where the popover opens. |
align | "start" | "center" | "end" | "center" | Alignment along the trigger. |
className | string | No default | Merged onto the trigger button. |
Known gaps#
Where the implementation and the system disagree today. Follow the system, not the gap.
InfoTip takes no open or onOpenChange, so it can't be opened from elsewhere, such as from a What's this? link in an empty state.
The hover delay (350ms) and close delay (150ms) are fixed and differ from the tooltip's 400ms, so a row with both opens them at different speeds.
The trigger's hit area is 24px everywhere. DESIGN.md asks for 32px targets on touch surfaces.