FLOWDocs

Typography

Geist font families and typographic primitives.

Flow UI ships two font families — Geist Sans and Geist Mono — and a small set of typographic React components that wrap the most common text roles.

Fonts

Both are self-hosted — the woff2 files are vendored into the package (src/styles/fonts/) and registered by fonts.css, so consumers bundle them directly with no external font request. Loaded with font-display: swap (FOUT, not FOIT) so text always paints immediately.

Geist Sans

Variable font, weights 100–900, with an italic axis. Use it for everything that isn't a monospace role.

font-family: var(--font-sans);

Geist Mono

Variable, weights 100–900, with italic. Use for code, keyboard keys, console output, anything where character alignment matters.

font-family: var(--font-mono);

Text size scale

Every size lives on a CSS variable. Tailwind utilities text-xs through text-9xl map to these tokens.

See Tokens → Size scale for the full list.

Components

Import from @flow-industries/ui/components/typography. Exports: Title, Subtitle, Overline, Paragraph, Mono.

View source on GitHub →

Usage:

import { Title, Subtitle, Overline, Paragraph, Mono } from "@flow-industries/ui/components/typography";

<Title size="lg">Welcome to Flow</Title>
<Subtitle size="lg">A decentralized platform.</Subtitle>
<Overline variant="brand">Now shipping</Overline>
<Paragraph>Some body copy that goes on for several words and lines.</Paragraph>
<Mono>bun add @flow-industries/ui</Mono>

Title sizes map to HTML tags: sm → <h3>, default → <h2>, md → <h2>, lg → <h1> — or pass level (1–6) to set the heading tag directly (<h1>–<h6>), keeping the visual size while the document outline stays correct. Subtitle exposes the same four sizes. Overline supports default and brand variants. Paragraph takes size — "xs" | "sm" | "default" | "lg" — and defaults to "sm", not "default".

Source:

components/ui/typography.tsx
import { cn } from "../../utils/cn";

const titleStyles = {
  sm: "text-sm font-semibold",
  default: "text-base font-bold tracking-tighter",
  md: "text-2xl font-bold tracking-tighter",
  lg: "text-3xl font-bold tracking-tighter",
} as const;

const titleTags = { sm: "h3", default: "h2", md: "h2", lg: "h1" } as const;

const headingTags = {
  1: "h1",
  2: "h2",
  3: "h3",
  4: "h4",
  5: "h5",
  6: "h6",
} as const;

function Title({
  className,
  size = "default",
  level,
  ...props
}: React.ComponentProps<"h1"> & {
  size?: "sm" | "default" | "md" | "lg";
  level?: 1 | 2 | 3 | 4 | 5 | 6;
}) {
  const Tag = level ? headingTags[level] : titleTags[size];
  return (
    <Tag
      data-slot="title"
      className={cn(titleStyles[size], className)}
      {...props}
    />
  );
}

const subtitleStyles = {
  sm: "text-xs font-medium text-muted-foreground",
  default: "text-sm font-medium text-muted-foreground",
  md: "text-base font-medium text-muted-foreground",
  lg: "text-lg font-medium text-muted-foreground",
} as const;

function Subtitle({
  className,
  size = "default",
  ...props
}: React.ComponentProps<"p"> & { size?: "sm" | "default" | "md" | "lg" }) {
  return (
    <p
      data-slot="subtitle"
      className={cn(subtitleStyles[size], className)}
      {...props}
    />
  );
}

function Overline({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<"span"> & { variant?: "default" | "brand" }) {
  return (
    <span
      data-slot="overline"
      className={cn(
        "text-sm font-[550] tracking-wide",
        variant === "brand" ? "text-brand-text" : "text-muted-foreground",
        className,
      )}
      {...props}
    />
  );
}

const paragraphVariants = {
  xs: "text-xs",
  sm: "text-sm",
  default: "text-base",
  lg: "text-lg",
} as const;

function Paragraph({
  className,
  size = "sm",
  ...props
}: React.ComponentProps<"p"> & { size?: "xs" | "sm" | "default" | "lg" }) {
  return (
    <p
      data-slot="paragraph"
      className={cn(
        "font-[450] leading-[150%] tracking-wide text-foreground/80",
        paragraphVariants[size],
        className,
      )}
      {...props}
    />
  );
}

function Mono({ className, ...props }: React.ComponentProps<"code">) {
  return (
    <code
      data-slot="mono"
      className={cn(
        "text-sm font-mono tracking-normal text-muted-foreground",
        className,
      )}
      {...props}
    />
  );
}

export { Mono, Overline, Paragraph, Subtitle, Title };

Direct Tailwind usage

Nothing stops you from writing classes directly — all Flow components accept className and merge via cn():

<h1 className="font-sans text-6xl font-bold tracking-tight">Hello</h1>
<code className="font-mono text-sm">example</code>

Use the components when you want a design-system-approved default; use raw classes when you need something bespoke.

On this page