component — brand

Logo

The quebi wordmark or the round q, in the right ink for the theme — ink on Daylight, light on Cinematic — switched by CSS so a prerendered page never flashes the wrong one. Pass srcInk/srcLight to point at your own hosted copies.

  • brand
  • logo
  • wordmark
  • mark
  • theme
  • image

use a logo when

  • The wordmark at the left of a nav bar (height 20, or 32 on a landing page) and in a footer.
  • The mark (the round q) where the name is already said nearby, at 36px or more.

don't

  • Don't recolour, outline, stretch or add effects to it — it ships in exactly two inks.
  • Don't retype the wordmark in Outfit; it is a drawing, not text.
  • Don't crowd it: keep clear space of at least the height of the q around it.
  • Don't use it as a page watermark. The giant mark behind a stage is the Stage's own glyph.

03 examples

Wordmark

The default: 20px for a nav bar, 32px in a landing page's top-left corner.

Mark

The round q on its own, at 36px or more — where the name is already said nearby.

Both inks

Each logo renders both files and the theme picks one. A `light` or `dark` class on any ancestor re-themes the subtree, so the same component reads on paper and on ink.

what to use instead

by job

By what the logo was doing.

A decorative mark behind a headline
  • Stage — Its `glyph` draws the mark in the glyph ink, hidden from assistive tech.

source

logo.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

import { cn } from "@/lib/utils"

/**
 * Logo — quebi design system
 *
 * The quebi wordmark, or the round q on its own, in the right ink for the
 * theme: ink on Daylight, light on Cinematic.
 *
 * Both files are rendered and the theme picks one with CSS, rather than the
 * component reading the theme and choosing a `src`. The site is prerendered
 * with no theme known at build time, so a choice made in React would bake one
 * ink into the HTML and swap it after hydration — a flash of the wrong logo on
 * every dark page load. The selectors follow the `.dark` class the theme sets
 * on `<html>`, and a `.light` subtree inside a dark page wins back the ink one.
 *
 * Keep clear space of at least the height of the q around it. Never recolour,
 * outline, stretch or add effects to it, and never retype the wordmark in
 * Outfit — it is a drawing, not text.
 */

/** Intrinsic aspect ratios of the shipped files, so the width is reserved before they load. */
const ASPECT = { wordmark: 762 / 240, mark: 1 } as const

const DEFAULT_HEIGHT = { wordmark: 20, mark: 36 } as const

const DEFAULT_SRC = {
  wordmark: { ink: "/brand/quebi-wordmark-ink.png", light: "/brand/quebi-wordmark-light.png" },
  mark: { ink: "/brand/quebi-mark-ink.png", light: "/brand/quebi-mark-light.png" },
} as const

export interface LogoProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
  /** `wordmark` (default) for navigation and footers; `mark`, the round q, where the name is already said. */
  variant?: "wordmark" | "mark"
  /**
   * Rendered height in px. Defaults to 20 for the wordmark (a nav bar) and 36
   * for the mark; 32 suits a landing page's top-left nav. Keep the mark at 36
   * or more.
   */
  height?: number
  /** Your own hosted copy of the ink (dark-on-light) file. */
  srcInk?: string
  /** Your own hosted copy of the light (light-on-dark) file. */
  srcLight?: string
  /** Accessible name. Defaults to "quebi". */
  label?: string
}

export function Logo({
  variant = "wordmark",
  height = DEFAULT_HEIGHT[variant],
  srcInk = DEFAULT_SRC[variant].ink,
  srcLight = DEFAULT_SRC[variant].light,
  label = "quebi",
  className,
  ...props
}: LogoProps) {
  const width = Math.round(height * ASPECT[variant])
  return (
    <span
      data-slot="logo"
      data-variant={variant}
      role="img"
      aria-label={label}
      className={cn("inline-block shrink-0 leading-none", className)}
      {...props}
    >
      <img
        data-ink="ink"
        src={srcInk}
        alt=""
        width={width}
        height={height}
        draggable={false}
        className="block max-w-none [.dark_&]:hidden [.dark_.light_&]:block"
      />
      <img
        data-ink="light"
        src={srcLight}
        alt=""
        width={width}
        height={height}
        draggable={false}
        className="hidden max-w-none [.dark_&]:block [.dark_.light_&]:hidden"
      />
    </span>
  )
}