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