Logo mark
Sixteen fictional companies' wordmarks — a small glyph and the name — for logo clouds, bylines and integrations.
Northwind, Keystone, Harbour, Riverside, Lumen, Alder, Kestrel, Meridian, Wavelength, Northgate, Tidewater, Copperline, Fieldstone, Brightwater, Solstice and Ironbark — none of them real, so a demo page never claims a customer it does not have. Each is drawn in the current colour: put text-muted-foreground on a logo cloud and every mark in it recedes together. A name set in capitals comes down one step of the type scale so the row holds one optical size. One role="img" named by the company, so a row of them reads as a list of names.
Install
$
npx shadcn@latest add @vibra/logo-markNeeds the @vibra registry in your components.json — set it up once.
Examples
import { LOGO_MARKS, LogoMark } from "@/components/ui/logo-mark"
export default function LogoMarkDemo() {
return (
<div className="flex w-full max-w-2xl flex-col items-center gap-6">
<p id="logo-mark-demo-caption" className="text-sm text-muted-foreground">
Trusted by teams at
</p>
<ul
aria-labelledby="logo-mark-demo-caption"
className="grid w-full grid-cols-2 place-items-center gap-x-6 gap-y-5 text-muted-foreground sm:grid-cols-4"
>
{LOGO_MARKS.map((name) => (
<li key={name}>
<LogoMark name={name} size="sm" />
</li>
))}
</ul>
</div>
)
}Sizes
sm, default and lg, in the ink.
import { LogoMark } from "@/components/ui/logo-mark"
const sizes = ["sm", "default", "lg"] as const
export default function LogoMarkSizes() {
return (
<div className="flex flex-col items-center gap-5 text-foreground">
{sizes.map((size) => (
<div key={size} className="flex flex-wrap items-center justify-center gap-x-8 gap-y-3">
<LogoMark name="Northwind" size={size} />
<LogoMark name="Kestrel" size={size} />
<LogoMark name="Tidewater" size={size} />
</div>
))}
</div>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| name | LogoName | — | Which company: one of LOGO_MARKS. |
| size | "sm" | "default" | "lg" | "default" | 14, 16 or 20px type, with an 18, 20 or 24px glyph. |
| LOGO_MARKS | readonly LogoName[] | — | The sixteen companies, in order. |
Dependencies
Registry
Source
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
/**
* Sixteen companies that do not exist, for every place a page shows who uses
* a product — a logo cloud, a testimonial's byline, an integrations grid.
* The sample data's own company, Northwind, is one of them. A real company's
* mark on a demo page is a claim nobody made, so none appears here.
*/
export const LOGO_MARKS = [
"Northwind",
"Keystone",
"Harbour",
"Riverside",
"Lumen",
"Alder",
"Kestrel",
"Meridian",
"Wavelength",
"Northgate",
"Tidewater",
"Copperline",
"Fieldstone",
"Brightwater",
"Solstice",
"Ironbark",
] as const
export type LogoName = (typeof LOGO_MARKS)[number]
/** A monoline stroke inside a filled mark's 24-unit grid. */
const LINE = { fill: "none", stroke: "currentColor", strokeWidth: 2.25, strokeLinecap: "round", strokeLinejoin: "round" } as const
/**
* Each company: a glyph on a 24-unit grid — solid shapes in the ink, a
* monoline where the idea is a line — and the way its name is set. Every name
* is the sans at a weight and case of its own, so a row of sixteen reads as
* sixteen companies rather than one font in sixteen colours.
*/
const LOGOS: Record<LogoName, { glyph: React.ReactNode; word: string; caps?: true }> = {
Northwind: {
glyph: <path d="M12 2.5 19.5 20.5 12 16.3 4.5 20.5Z" />,
word: "font-semibold",
},
Keystone: {
glyph: <path d="M5 3.5h14c.9 0 1.6.9 1.4 1.8l-3.6 13.9c-.2.7-.8 1.3-1.6 1.3H8.8c-.8 0-1.4-.5-1.6-1.3L3.6 5.3C3.4 4.4 4.1 3.5 5 3.5Z" />,
word: "font-bold uppercase tracking-widest",
caps: true,
},
Harbour: {
glyph: (
<>
<path d="M5 13.5a7 7 0 0 1 14 0Z" />
<rect x="2.5" y="15.5" width="19" height="2.4" rx="1.2" />
<rect x="6" y="19.6" width="12" height="2.4" rx="1.2" />
</>
),
word: "font-medium",
},
Riverside: {
glyph: <path {...LINE} d="M7.5 3.5c5.5 2.7 5.5 5.6.8 8.5s-4.7 5.8.8 8.5M15 3.5c5.5 2.7 5.5 5.6.8 8.5s-4.7 5.8.8 8.5" />,
word: "font-semibold lowercase",
},
Lumen: {
glyph: (
<>
<circle cx="12" cy="12" r="4.6" />
<path {...LINE} d="M12 2.5v2M12 19.5v2M2.5 12h2M19.5 12h2M5.3 5.3l1.4 1.4M17.3 17.3l1.4 1.4M5.3 18.7l1.4-1.4M17.3 6.7l1.4-1.4" />
</>
),
word: "font-medium lowercase tracking-wide",
},
Alder: {
glyph: <path d="M12 2.2 17.6 10h-2.9l4.1 6.6h-5.4V21h-2.8v-4.4H5.2L9.3 10H6.4Z" />,
word: "font-semibold",
},
Kestrel: {
glyph: <path d="M2 7.6c3.4-.4 6.7.9 8.8 3.6L12 12.8l1.2-1.6c2.1-2.7 5.4-4 8.8-3.6-2.5 1.5-4.3 3.7-5.3 6.3l-1.2 2.6L12 20.4l-3.5-3.9-1.2-2.6C6.3 11.3 4.5 9.1 2 7.6Z" />,
word: "font-semibold uppercase tracking-wider",
caps: true,
},
Meridian: {
glyph: (
<>
<circle {...LINE} cx="12" cy="12" r="9" />
<ellipse {...LINE} cx="12" cy="12" rx="3.8" ry="9" />
<path {...LINE} d="M3.5 12h17" />
</>
),
word: "font-medium",
},
Wavelength: {
glyph: <path {...LINE} strokeWidth={2.5} d="M2.5 12c2.4-6.2 4.8-6.2 7.2 0s4.8 6.2 7.2 0 3.6-4.7 4.6-2.4" />,
word: "font-bold lowercase",
},
Northgate: {
glyph: <path d="M3.5 21.5V10.5a8.5 8.5 0 0 1 17 0v11h-4.3v-11a4.2 4.2 0 0 0-8.4 0v11Z" />,
word: "font-semibold",
},
Tidewater: {
glyph: (
<path
d="M12 2.2c4.3 5.2 7.3 9 7.3 12.6a7.3 7.3 0 0 1-14.6 0c0-3.6 3-7.4 7.3-12.6Zm-4.6 13c1.5-1.1 3-1.1 4.6 0s3.1 1.1 4.6 0v1.9c-1.5 1.1-3 1.1-4.6 0s-3.1-1.1-4.6 0Z"
fillRule="evenodd"
/>
),
word: "font-medium",
},
Copperline: {
glyph: <path {...LINE} d="M18.4 6.2a8.6 8.6 0 1 0 0 11.6M15.2 9.3a4.2 4.2 0 1 0 0 5.4" />,
word: "font-medium uppercase tracking-widest",
caps: true,
},
Fieldstone: {
glyph: (
<>
<ellipse cx="12" cy="18.8" rx="8" ry="3" />
<ellipse cx="12" cy="12.6" rx="5.6" ry="2.5" />
<ellipse cx="12" cy="7.2" rx="3.4" ry="2.1" />
</>
),
word: "font-semibold",
},
Brightwater: {
glyph: <path d="m12 2 2.3 7.7L22 12l-7.7 2.3L12 22l-2.3-7.7L2 12l7.7-2.3Z" />,
word: "font-medium tracking-wide",
},
Solstice: {
glyph: (
<>
<path d="M6 17a6 6 0 0 1 12 0Z" />
<rect x="2.5" y="18.8" width="19" height="2.4" rx="1.2" />
<path {...LINE} d="M12 3.5v3.2M4.6 7.6l2.2 2.2M19.4 7.6l-2.2 2.2" />
</>
),
word: "font-bold uppercase tracking-wider",
caps: true,
},
Ironbark: {
glyph: <path d="m12 2 8.7 5v10L12 22l-8.7-5V7Zm0 6a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z" fillRule="evenodd" />,
word: "font-bold",
},
}
/**
* A name set in capitals comes down one step of the scale, so its cap height
* sits where a mixed-case name's x-height and ascenders do, and a row of marks
* holds one optical size.
*/
const CAPS_SIZE = { sm: "text-xs", default: "text-sm", lg: "text-base" } as const
const logoMarkVariants = cva("inline-flex shrink-0 items-center whitespace-nowrap leading-none", {
variants: {
size: {
sm: "gap-1.5 text-sm [&_svg]:size-4.5",
default: "gap-2 text-base [&_svg]:size-5",
lg: "gap-2.5 text-xl [&_svg]:size-6",
},
},
defaultVariants: { size: "default" },
})
export type LogoMarkProps = Omit<React.ComponentProps<"span">, "children"> &
VariantProps<typeof logoMarkVariants> & {
name: LogoName
}
/**
* One fictional company's mark: its glyph and its name, drawn in the current
* colour — set `text-muted-foreground` on a logo cloud and every mark in it
* recedes together. It is one image to assistive technology, named by the
* company, so a row of them reads as a list of names and nothing else.
*/
function LogoMark({ className, name, size, ...props }: LogoMarkProps) {
const logo = LOGOS[name]
const scale = size ?? "default"
return (
<span
data-slot="logo-mark"
data-logo={name.toLowerCase()}
data-size={scale}
role="img"
aria-label={name}
className={cn(logoMarkVariants({ size: scale }), className)}
{...props}
>
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false" className="shrink-0">
{logo.glyph}
</svg>
<span className={cn("font-sans", logo.word, logo.caps && CAPS_SIZE[scale])}>{name}</span>
</span>
)
}
export { LogoMark }