Skip to content
Highlight

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

tsx
import { Logo, LOGOS, logoNames } from '@highlight/ui'

Change the props; the preview and the snippet update together.

Props
Name
Size
tsx
<Logo name="slack" />

One component, one name. The mark names the thing a tile connects to, so it carries the accessible name.

tsx
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

Google Calendargoogle-calendar
Gmailgmail
Google Docsgoogle-docs
Google Analyticsgoogle-analytics
Slackslack
GitHubgithub
Notionnotion
Linearlinear
Linearlinear-dark
Microsoftmicrosoft
Outlookoutlook
Granolagranola
Cursorcursor
Claudeclaude
Ashbyashby
Greenhousegreenhouse
Asanaasana
PostHogposthog
Metameta
Atlassianatlassian
Discorddiscord

Meetings

Google Meetgoogle-meet
Microsoft Teamsmicrosoft-teams

Browsers

Arcarc
Bravebrave
Chromechrome
Edgeedge
Firefoxfirefox
Safarisafari

Platforms

Googlegoogle
Google Workspacegoogle-workspace
Figmafigma
Salesforcesalesforce
tsx
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.

size="xs"12px, beside text-xs
size="sm"14px, beside text-sm
size="md"16px, beside text-md, in a well
size="lg"20px, a row or a tile
tsx
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=&quot;{size}&quot;</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.

Slack
Linear
Notion
GitHub
xs
sm
md
lg
tsx
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.

xs
sm
md
lg
tsx
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&rsquo;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>  )}

Unsized, so the button gives it the same size as any icon in that slot.

tsx
import { Button, Logo } from '@highlight/ui' export default function LogoInAButton() {  return (    <Button variant="secondary" left={<Logo name="github" />}>      Continue with GitHub    </Button>  )}

Beside a label, the mark takes the glyph slot an icon would.

Google Calendar
tsx
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

tsx
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.

This page's theme
Forced dark
tsx
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&apos;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.

linear
linear-dark
tsx
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.

PropTypeDefaultDescription
nameLogoName—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.