Foundations

Typography

Geist sans for everything that reads, Geist mono for everything that's code. Every role on this page is a copy-pasteable recipe — drop the className verbatim and you get the right size, weight, tracking, and balance behavior.

Headings

One h1 per page. h2 sections within. h3/h4 carry subsections. All headings use tight tracking and the text-balance rule so two-line headings don't break on a lonely word.

h1

Built for the recruiting cycle

export function TypographyH1() {
return (
<h1 className="scroll-m-20 text-4xl font-bold tracking-tight text-balance">
Built for the recruiting cycle
</h1>
)
}

h2

The class of 2027

export function TypographyH2() {
return (
<h2 className="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0">
The class of 2027
</h2>
)
}

h3

Combine scores at LSU's regional camp

export function TypographyH3() {
return (
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">
Combine scores at LSU's regional camp
</h3>
)
}

h4

Notes from the head coach

export function TypographyH4() {
return (
<h4 className="scroll-m-20 text-xl font-semibold tracking-tight">
Notes from the head coach
</h4>
)
}

Body

The bulk of every page. Lead for the page intro, p for everything else. Large for emphatic statements; small/muted for metadata and helper text.

p

A recruiter's day starts at five in the morning and ends well after midnight. Most of it is film. The rest of it is phone calls — to coaches, to parents, to anyone who'll pick up. The only way to do this job at scale is to throw away everything that doesn't move a recruit closer to a decision.

export function TypographyP() {
return (
<p className="leading-7 [&:not(:first-child)]:mt-6">
A recruiter's day starts at five in the morning and ends well after
midnight. Most of it is film. The rest of it is phone calls — to
coaches, to parents, to anyone who'll pick up.
</p>
)
}

Lead

Marcus moved from third on the depth chart to QB1 in week 4 after Jenkins went down with an ACL. Six wins later, LSU pulled the offer off the table.

export function TypographyLead() {
return (
<p className="text-xl text-body-light">
Marcus moved from third on the depth chart to QB1 in week 4 after
Jenkins went down with an ACL.
</p>
)
}

Large

Built for coaches, not for everyone else.
export function TypographyLarge() {
return <div className="text-lg font-semibold">Built for coaches, not for everyone else.</div>
}

Small

Updated 3 minutes ago by Coach Patel.
export function TypographySmall() {
return (
<small className="text-sm leading-none font-medium">
Updated 3 minutes ago by Coach Patel.
</small>
)
}

Muted

We'll only show this to verified college recruiters.

export function TypographyMuted() {
return (
<p className="text-sm text-body-light">
We'll only show this to verified college recruiters.
</p>
)
}

Inline elements

Things you reach for mid-sentence: code chips, keyboard keys, links. Each one has a specific job — don't substitute.

Inline code

@radix-ui/react-alert-dialog
export function TypographyInlineCode() {
return (
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">
@radix-ui/react-alert-dialog
</code>
)
}

Inline code (in a paragraph)

Run the audit with yarn upnext:audit before pushing. It flags any hardcoded color values, including ones hiding inside style={...} props.

<p className="leading-7">
Run the audit with{" "}
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">
yarn upnext:audit
</code>{" "}
before pushing.
</p>

kbd

Press ⌘K to open the recruit search, then ↵ to commit.

<p className="text-sm leading-7">
Press{" "}
<kbd className="inline-flex items-center justify-center rounded-sm bg-muted px-1.5 py-0.5 font-sans text-xs text-body">
⌘K
</kbd>{" "}
to open the recruit search.
</p>

Link

import { Link } from "@/components/upnext";
<p className="leading-7">
Read more about{" "}
<Link href="#">how we grade ceiling and floor</Link>.
{/* Primary tone for CTA emphasis: */}
<Link href="#" tone="primary">View the full rubric</Link>
</p>

Block elements

Structured content: pulled quotes, lists, tables. Each is intentionally minimal — borders and indentation, no chrome.

blockquote

"You don't recruit a player — you recruit a family. The best programs figure that out by the second phone call."
export function TypographyBlockquote() {
return (
<blockquote className="mt-6 border-l-2 pl-6 italic">
"You don't recruit a player — you recruit a family. The best
programs figure that out by the second phone call."
</blockquote>
)
}

list

  • Read film three times before the first phone call.
  • Take notes in pen — phone notes you forget.
  • Never promise an offer in writing before the staff meeting.
export function TypographyList() {
return (
<ul className="my-6 ml-6 list-disc [&>li]:mt-2">
<li>Read film three times before the first phone call.</li>
<li>Take notes in pen — phone notes you forget.</li>
<li>Never promise an offer in writing before the staff meeting.</li>
</ul>
)
}

Ordered list

  1. Watch film at game speed first; only then go frame by frame.
  2. Identify three plays that show the ceiling.
  3. Identify three plays that show the floor.
<ol className="my-6 ml-6 list-decimal marker:text-body [&>li]:mt-2">
<li>Watch film at game speed first; only then go frame by frame.</li>
<li>Identify three plays that show the ceiling.</li>
<li>Identify three plays that show the floor.</li>
</ol>

Table

RecruitPosition40-ydGPA
Marcus ReedQB4.62s3.42
Tariq BellWR4.51s3.81
export function TypographyTable() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Recruit</TableHead>
<TableHead>Position</TableHead>
<TableHead className="tabular-nums">40-yd</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Marcus Reed</TableCell>
<TableCell>QB</TableCell>
<TableCell className="tabular-nums">4.62s</TableCell>
</TableRow>
</TableBody>
</Table>
)
}

Full type scale

The px reference. Twelve sizes from 60px display to 11px micro. Tracking tightens as size grows; weight relaxes as size shrinks. Every utility above resolves down to one of these.

namespecsample

display-2xl

60px / 1.05 · 600

UpNext — talent, signed.

display-xl

48px / 1.10 · 600

UpNext — talent, signed.

display-lg

36px / 1.15 · 600

UpNext — talent, signed.

h1

30px / 1.20 · 600

UpNext — talent, signed.

h2

24px / 1.25 · 600

UpNext — talent, signed.

h3

20px / 1.30 · 600

UpNext — talent, signed.

h4

18px / 1.35 · 600

UpNext — talent, signed.

body-lg

16px / 1.55 · 400

UpNext — talent, signed.

body

14px / 1.50 · 400

UpNext — talent, signed.

body-sm

13px / 1.45 · 400

UpNext — talent, signed.

caption

12px / 1.40 · 500

UpNext — talent, signed.

micro

11px / 1.35 · 500

UpNext — talent, signed.

When to reach for mono

Geist mono is a precision instrument. Default to sans; bring mono only when the content is literally code or token-shaped.

✓

Code samples, file paths, CSS variable names.

✓

Color values for reference: oklch(0.563 0.149 225).

✓

Keyboard shortcut chips (⌘K, Ctrl+Shift+P).

✕

Section labels in sidebar/UI nav — sans uppercase reads cleaner.

✕

Hex values in a designer-facing readout (use sans medium lowercase, like Figma).

✕

Captions, metadata, body text — mono is functional, not editorial.