Skip to contentVibraUI
Data display

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-mark

Needs the @vibra registry in your components.json — set it up once.

Examples

Sizes

sm, default and lg, in the ink.

Props

PropTypeDefaultDescription
nameLogoName—Which company: one of LOGO_MARKS.
size"sm" | "default" | "lg""default"14, 16 or 20px type, with an 18, 20 or 24px glyph.
LOGO_MARKSreadonly LogoName[]—The sixteen companies, in order.

Dependencies

Source

components/ui/logo-mark.tsx
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 }