V2 componentsUpdated Oct 3, 2026
Logo
The third-party marks a screen has to show: an integration tile, a provider row, the well on an onboarding card. A separate set from the icons, and not mergeable with them.
Source: packages/ui/src/components/logo
import { Logo, LOGOS, logoNames } from '@highlight/ui'Change the props; the preview and the snippet update together.
<Logo name="slack" />One component, one name. The mark names the thing a tile connects to, so it carries the accessible name.
import { Logo } from '@highlight/ui' export default function LogoBasic() { return ( <div className="flex items-center gap-6"> <Logo name="slack" /> <Logo name="github" /> <Logo name="google-calendar" /> <Logo name="linear" /> <Logo name="notion" /> </div> )}Every mark the app shows, vendored from svgl.app into components/logo/nested/marks/, one file per mark. Regenerate a mark rather than hand-editing it.
Integrations
Meetings
Browsers
Platforms
import { LOGOS, Logo, type LogoName } from '@highlight/ui' /** Where each mark appears in the product. */const GROUPS: Array<{ title: string; names: Array<LogoName> }> = [ { title: 'Integrations', names: [ 'google-calendar', 'gmail', 'google-docs', 'google-analytics', 'slack', 'github', 'notion', 'linear', 'linear-dark', 'microsoft', 'outlook', 'granola', 'cursor', 'claude', 'ashby', 'greenhouse', 'asana', 'posthog', 'meta', 'atlassian', 'discord', ], }, { title: 'Meetings', names: ['google-meet', 'microsoft-teams'] }, { title: 'Browsers', names: ['arc', 'brave', 'chrome', 'edge', 'firefox', 'safari'] }, { title: 'Platforms', names: ['google', 'google-workspace', 'figma', 'salesforce'] },] export default function LogoSet() { return ( <div className="flex w-full flex-col gap-6"> {GROUPS.map((group) => ( <div key={group.title} className="flex flex-col gap-2.5"> <h3 className="text-foreground-secondary text-xs font-medium">{group.title}</h3> <div className="grid w-full grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-3"> {group.names.map((name) => ( <div key={name} className="bg-background-raised corner-smooth flex items-center gap-2.5 rounded-xl border px-3 py-2.5" > <Logo name={name} /> <span className="min-w-0 flex-1"> <span className="text-foreground block truncate text-sm">{LOGOS[name].name}</span> <span className="text-foreground-secondary block truncate text-xs">{name}</span> </span> </div> ))} </div> </div> ))} </div> )}The icon steps and nothing of its own. A mark beside a label lines up with the glyph it replaces; omitted, a primitive that sizes its icons sizes the mark, and alone it is 16.
import { Logo } from '@highlight/ui' const SIZES = [ { size: 'xs', use: '12px, beside text-xs' }, { size: 'sm', use: '14px, beside text-sm' }, { size: 'md', use: '16px, beside text-md, in a well' }, { size: 'lg', use: '20px, a row or a tile' },] as const export default function LogoSizes() { return ( <div className="flex items-end gap-10"> {SIZES.map(({ size, use }) => ( <div key={size} className="flex flex-col items-start gap-2"> <Logo name="slack" size={size} /> <span className="text-foreground-secondary text-xs">size="{size}"</span> <span className="text-foreground-secondary text-xs">{use}</span> </div> ))} </div> )}On an integration tile or onboarding card the mark sits in Logo.Well; the button only says what to do. Its sizes follow the HighlightLogo.Tile ramp, so a well and a tile of one size share a box and a corner.
import { Button, LOGOS, Logo } from '@highlight/ui' const SIZES = ['xs', 'sm', 'md', 'lg'] as const export default function LogoWell() { return ( <div className="flex w-full max-w-md flex-col gap-6"> <div className="grid grid-cols-2 gap-3"> {(['slack', 'linear', 'notion', 'github'] as const).map((name) => ( <div key={name} className="bg-background corner-smooth shadow-xs flex items-center gap-3 rounded-xl p-3"> <Logo.Well> <Logo name={name} /> </Logo.Well> <span className="type-ui-sm text-foreground min-w-0 flex-1 truncate font-medium">{LOGOS[name].name}</span> <Button size="sm" variant="secondary"> Connect </Button> </div> ))} </div> <div className="flex items-end gap-4"> {SIZES.map((size) => ( <div key={size} className="flex flex-col items-center gap-2"> <Logo.Well size={size}> <Logo name="slack" /> </Logo.Well> <span className="text-foreground-secondary text-xs">{size}</span> </div> ))} </div> </div> )}The well frames any glyph, not only a brand mark. A Lucide icon takes the same step as a logo would, so a row without a provider, like a request or a fallback, keeps the same square.
Don’t see a tool you use?
Request an integration.
import { ArrowUpRight, MessageSquarePlus } from 'lucide-react'import { Button, Logo } from '@highlight/ui' const SIZES = ['xs', 'sm', 'md', 'lg'] as const export default function LogoWellIcon() { return ( <div className="flex w-full max-w-md flex-col gap-6"> <div className="bg-background corner-smooth shadow-xs flex items-center gap-3 rounded-xl p-3"> <Logo.Well className="text-foreground-secondary"> <MessageSquarePlus /> </Logo.Well> <div className="min-w-0 flex-1"> <p className="type-ui-sm text-foreground font-medium">Don’t see a tool you use?</p> <p className="type-prose-sm text-foreground-secondary">Request an integration.</p> </div> <Button size="sm" variant="outline" right={<ArrowUpRight />}> Request </Button> </div> <div className="flex items-end gap-4"> {SIZES.map((size) => ( <div key={size} className="flex flex-col items-center gap-2"> <Logo.Well size={size} className="text-foreground-secondary"> <MessageSquarePlus /> </Logo.Well> <span className="text-foreground-secondary text-xs">{size}</span> </div> ))} </div> </div> )}Beside a label, the mark takes the glyph slot an icon would.
import { Cell, Logo } from '@highlight/ui' export default function LogoProviderRow() { return ( <div className="w-80"> <Cell onClick={() => {}}> <Cell.Header showChevron={false}> <Cell.Icon> <Logo name="google-calendar" /> </Cell.Icon> <Cell.Title>Google Calendar</Cell.Title> </Cell.Header> </Cell> </div> )}Sized to the text it sits in: sm beside text-sm.
Synced from Notion 2 minutes ago
import { Logo } from '@highlight/ui' export default function LogoInline() { return ( <p className="type-ui-sm text-foreground-secondary m-0 flex items-center gap-1"> Synced from <Logo name="notion" size="sm" /> Notion 2 minutes ago </p> )}Most marks are the same drawing on either ground. GitHub and Cursor are monochrome and take currentColor, so they follow the text beside them. Notion is a pair, its body var(--background) and its glyph var(--foreground), so it reads as Notion’s black on white whatever the text colour.
import { Logo } from '@highlight/ui' function Row() { return ( <div className="flex gap-6 px-8 py-6"> <Logo name="github" /> <Logo name="notion" /> <Logo name="cursor" /> </div> )} export default function LogoThemes() { return ( <div className="flex flex-wrap gap-10"> <figure className="m-0 flex flex-col items-center gap-3"> <div className="bg-background-raised text-foreground corner-smooth rounded-xl border"> <Row /> </div> <figcaption className="text-foreground-secondary text-xs">This page's theme</figcaption> </figure> {/* The forced-dark stage restates text-foreground: an inherited colour was resolved further up. */} <figure className="dark m-0 flex flex-col items-center gap-3"> <div className="bg-background text-foreground corner-smooth rounded-xl border"> <Row /> </div> <figcaption className="text-foreground-secondary text-xs">Forced dark</figcaption> </figure> </div> )}Linear ships a second drawing for dark tiles. Ask for it by name; the component does not swap it.
import { Logo } from '@highlight/ui' export default function LogoLinearDark() { return ( <div className="flex items-center gap-8"> <figure className="m-0 flex flex-col items-center gap-2"> <Logo name="linear" size="lg" /> <figcaption className="text-foreground-secondary text-xs">linear</figcaption> </figure> <figure className="m-0 flex flex-col items-center gap-2"> <Logo name="linear-dark" size="lg" /> <figcaption className="text-foreground-secondary text-xs">linear-dark</figcaption> </figure> </div> )}The root, then any compound sub-components. Every part accepts className and forwards the rest of its props to the element or Base UI part beneath it.
Logo- An svg of the named third-party logo, sized by size.
Logo.Well- The tinted square a mark sits in on an integration tile or onboarding card. Its size follows the HighlightLogo.Tile ramp (xs 24, sm 32, md 40 by default, lg 56px) and sizes an unsized Logo inside it to 12, 14, 16 or 20.
Props added on top of the underlying element or Base UI part.
| Prop | Type | Default | Description |
|---|---|---|---|
name | LogoName | — | The mark, by key: 'slack', 'google-calendar', 'github'… Every key is listed in the Set example. |
size | 'xs'|'sm'|'md'|'lg'|number | — | An icon step (12, 14, 16, 20px), or pixels like Lucide. Omitted, a primitive that sizes its icons (Button, Cell.Icon, Logo.Well) sizes the mark; alone it is 16. |
- Not an icon: a logo never takes an icon’s sizing or stroke, and never enters the Lucide set. A screen that wants a mark to take the colour of the text beside it wants an icon.
- The owner’s drawing: do not recolour a multi-colour logo to fit a surface, flatten it, or redraw it. If a mark will not sit on a ground, change the ground.
- Pinning a ground with .dark moves anything that names a token and nothing that inherited a resolved colour, so restate text-foreground on a forced-dark stage.
- Six catalog providers have no mark yet: Google Ads, HubSpot, Amplitude, Customer.io, Gem and Juicebox. Each needs an owner-published SVG before it joins the set.
- Highlight’s own mark is the exception, the one logo the system recolours. It lives on Foundations: Branding.