Skip to content

feat: migrate typography to Urbanist + Manrope#10

Merged
Muzzamil1 merged 2 commits into
mainfrom
claude/typography-bonuz-standard
Apr 17, 2026
Merged

feat: migrate typography to Urbanist + Manrope#10
Muzzamil1 merged 2 commits into
mainfrom
claude/typography-bonuz-standard

Conversation

@Mostafatalaat770

Copy link
Copy Markdown
Member

Summary

  • Swaps body/UI typography from Space Grotesk to Urbanist (latin 300–800, via next/font/google)
  • Adds Manrope (latin 300–800) for h1h6
  • Keeps Noto Sans Arabic / Noto Sans SC loaders intact for ar/zh locales (latin-only fonts can't render those scripts)
  • Incidentally fixes a latent selector bug: old html[lang="zh"] override never matched — layout sets lang="zh-Hans". New rules use [lang="zh-Hans"].

CSS-only migration — no component (.tsx) changes. Diff: 2 files.

Test plan

  • Run pnpm dev and load /en — DevTools → Computed → body font-family resolves to Urbanist (plus Urbanist Fallback)
  • Inspect any <h1> — resolves to Manrope
  • Load /ar — body renders in Noto Sans Arabic; RTL layout unaffected
  • Load /zh — body renders in Noto Sans SC (verify this now actually applies, previously silently broken)
  • Load /de — body renders in Urbanist (latin fallback for German)
  • Lighthouse / Network — no font layout shift, swap display behavior visible
  • CSP: no console errors about blocked font resources

Copilot AI review requested due to automatic review settings April 17, 2026 07:58
@vercel

vercel Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bounz-tech Ready Ready Preview, Comment Apr 17, 2026 10:00am

Request Review

@claude

claude Bot commented Apr 17, 2026

Copy link
Copy Markdown

Code Review — feat: migrate typography to Urbanist + Manrope

Overall this is a clean, well-scoped migration. The approach is sound — offloading latin fonts to next/font for automatic optimization while keeping Google Fonts only for scripts next/font can't handle. The zh-Hans selector bug fix is a valuable incidental fix. A few items worth addressing before merge:


Issues

1. Missing fallbacks on the explicit element rule (globals.css)

/* Current */
p, li, label, button, input, textarea, select {
  font-family: var(--font-urbanist);  /* no fallback */
}

/* Should match the html/body rule */
p, li, label, button, input, textarea, select {
  font-family: var(--font-urbanist), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

If --font-urbanist is not defined (e.g. an unmatched code path, future route without the variable), var() with no fallback resolves to inherit, which may silently cascade an unintended font rather than falling back gracefully. The html, body rule above already includes the system font chain — this rule should too for consistency.

2. Redundant selectors — p, li, label don't need explicit font-family

p, li, and label inherit font-family from body normally. The explicit rule is only needed for form elements (button, input, textarea, select) which browsers notoriously don't inherit font from. Consider simplifying:

/* Only form elements need the override */
button, input, textarea, select {
  font-family: var(--font-urbanist), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

This also makes the locale overrides below less verbose (no need to list p, li, label in the html[lang] blocks).

3. Weight mismatch between font loaders

next/font loads Urbanist/Manrope at weights 300–800, but the Google Fonts URL for Noto fonts only loads 400, 500, 600. This means font-weight: 300, 700, or 800 headings/body text will silently fallback to the nearest weight in Noto locales (ar, zh). Either restrict Urbanist/Manrope to the same weight range or extend Noto to match.

4. Tailwind config not updated (potential gap)

If the project uses Tailwind's font-sans utility or a custom fontFamily theme key anywhere in components, those won't pick up Urbanist automatically. It's worth checking whether tailwind.config.* needs:

theme: {
  extend: {
    fontFamily: {
      sans: ['var(--font-urbanist)', ...defaultTheme.fontFamily.sans],
      heading: ['var(--font-manrope)', ...defaultTheme.fontFamily.sans],
    },
  },
},

If no Tailwind font utilities are used and font is set entirely via CSS, this is a non-issue — just worth confirming.


Minor / Nits

  • display: "swap" is the default for next/font/google in Next.js 13+, so it's redundant (harmless, but adds noise).
  • The scroll-smooth Tailwind class on <html> applies scroll-behavior: smooth unconditionally, while the CSS @media (prefers-reduced-motion: no-preference) block is correctly gated. These conflict — this is pre-existing, but worth noting if accessibility is a concern.

What's Good

  • Conditional preconnect + stylesheet loading only for non-latin locales is correct and avoids unnecessary DNS lookups for latin users.
  • zh-Hans selector fix is correct — locale param is "zh" while htmlLang becomes "zh-Hans", and the CSS/font loading logic consistently uses locale for branching. Well handled.
  • Font loaders declared at module scope (not inside the component) is the correct pattern for next/font to avoid re-instantiation on every render.
  • Conditional rendering of the <> fragment instead of always emitting empty <link> tags is clean.

Summary: Blocking on item #1 (missing fallbacks) and worth a quick check on #4 (Tailwind config). Items #2 and #3 are recommendations to improve consistency. The core approach is solid.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the app’s typography system to use Urbanist for body/UI text and Manrope for headings via next/font/google, while keeping locale-specific non‑Latin fonts (Arabic/Chinese) loaded from Google Fonts and fixing the zh language selector mismatch (zh-Hans).

Changes:

  • Update global CSS to use --font-urbanist (body/UI) and --font-manrope (headings), with locale-specific overrides for ar and zh-Hans.
  • Load Urbanist + Manrope via next/font/google and attach their variable classes to the <html> element.
  • Only inject external Google Fonts <link> tags for Arabic/Chinese locales (Noto Sans Arabic / Noto Sans SC).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
app/globals.css Switches base and locale-specific font-family rules to Urbanist/Manrope variables and updates zh-Hans selector.
app/[locale]/layout.tsx Adds next/font loaders for Urbanist/Manrope, applies variable classes on <html>, and conditionally loads Noto fonts for non‑Latin locales.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/globals.css Outdated
html[lang="zh-Hans"] input,
html[lang="zh-Hans"] textarea,
html[lang="zh-Hans"] select {
font-family: "Noto Sans SC", var(--font-urbanist), sans-serif;

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same var() invalidation issue for the Simplified Chinese override: var(--font-urbanist) has no fallback, so if the variable isn't defined this whole font-family declaration is dropped and the zh-Hans override may not apply. Add a fallback argument to the var() (or remove it from this rule) to keep the declaration valid in all render paths.

Suggested change
font-family: "Noto Sans SC", var(--font-urbanist), sans-serif;
font-family: "Noto Sans SC",
var(--font-urbanist, -apple-system, BlinkMacSystemFont, "Segoe UI"),
sans-serif;

Copilot uses AI. Check for mistakes.
Comment thread app/globals.css Outdated
html[lang="zh-Hans"] h4,
html[lang="zh-Hans"] h5,
html[lang="zh-Hans"] h6 {
font-family: "Noto Sans SC", var(--font-manrope), sans-serif;

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue for zh-Hans headings: var(--font-manrope) without a fallback can invalidate the entire font-family declaration, preventing the locale-specific font from applying in contexts where the font variable class isn't present. Use var(--font-manrope, ...) (or remove the var() here) so the rule is always valid.

Suggested change
font-family: "Noto Sans SC", var(--font-manrope), sans-serif;
font-family: "Noto Sans SC",
var(--font-manrope, -apple-system, BlinkMacSystemFont, "Segoe UI"),
sans-serif;

Copilot uses AI. Check for mistakes.
Comment thread app/globals.css
Comment on lines +4 to +8
html,
body {
font-family: var(--font-urbanist), -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
}

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

font-family uses var(--font-urbanist) without a fallback value. If the urbanist.variable class is not present on <html> (e.g., global error / not-found pages that render their own <html>), the CSS variable is undefined and the whole font-family declaration becomes invalid, potentially falling back to the browser default font. Use a fallback in var() (or define default --font-urbanist/--font-manrope values) so typography degrades predictably outside the locale layout.

Copilot uses AI. Check for mistakes.
Comment thread app/globals.css
Comment on lines +16 to +24
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-manrope), -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
}

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the body font: font-family: var(--font-manrope), ... becomes an invalid declaration if --font-manrope is undefined (e.g., pages not wrapped by the locale layout). Consider using var(--font-manrope, <system font stack>) or defining a default value for --font-manrope to avoid unexpected serif fallback on headings.

Copilot uses AI. Check for mistakes.
Comment thread app/globals.css Outdated
input,
textarea,
select {
font-family: var(--font-urbanist);

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

font-family: var(--font-urbanist); will also be invalid anywhere --font-urbanist isn't set (same failure mode as the html, body rule). Adding a fallback to var() (or ensuring --font-urbanist is always defined globally) prevents these elements from dropping to browser-default fonts on pages that don't apply the font variable classes.

Suggested change
font-family: var(--font-urbanist);
font-family: var(--font-urbanist), -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;

Copilot uses AI. Check for mistakes.
Comment thread app/[locale]/layout.tsx
Comment on lines +8 to +13
const urbanist = Urbanist({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700", "800"],
variable: "--font-urbanist",
display: "swap",
});

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both Urbanist/Manrope are configured to preload 6 weights (300–800). Each weight typically becomes a separate font file with next/font, so this can significantly increase download size. Consider limiting the weight arrays to only the weights actually used in the app (e.g., 400/500/600/700 if those are the only Tailwind weights in use) to reduce payload and improve LCP.

Copilot uses AI. Check for mistakes.
Comment thread app/globals.css Outdated
html[lang="ar"] input,
html[lang="ar"] textarea,
html[lang="ar"] select {
font-family: "Noto Sans Arabic", var(--font-urbanist), sans-serif;

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this locale-specific font stack, var(--font-urbanist) has no fallback. If the CSS variable isn't defined, the entire font-family declaration becomes invalid (even though the first family is "Noto Sans Arabic"), so the locale override may not apply at all. Use var(--font-urbanist, <fallback stack>) here (or remove the var() from this rule) to keep the override working when the variable class isn't present.

Suggested change
font-family: "Noto Sans Arabic", var(--font-urbanist), sans-serif;
font-family: "Noto Sans Arabic",
var(--font-urbanist, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);

Copilot uses AI. Check for mistakes.
Comment thread app/globals.css Outdated
html[lang="ar"] h4,
html[lang="ar"] h5,
html[lang="ar"] h6 {
font-family: "Noto Sans Arabic", var(--font-manrope), sans-serif;

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same var() invalidation issue in the Arabic heading override: if --font-manrope isn't defined, this font-family declaration becomes invalid and headings won't reliably use Noto Sans Arabic. Add a fallback argument to var(--font-manrope, ...) (or remove var() from this locale-specific rule) so the override always remains valid.

Suggested change
font-family: "Noto Sans Arabic", var(--font-manrope), sans-serif;
font-family: "Noto Sans Arabic",
var(--font-manrope, -apple-system, BlinkMacSystemFont, "Segoe UI"),
sans-serif;

Copilot uses AI. Check for mistakes.
Move the system font stack INSIDE var(--font-*, ...) so declarations stay
valid when the next/font variable class is absent from <html>. Without
this, CSS invalidates the entire font-family property (IACVT) and text
drops to browser default.

Affected render path: app/global-error.tsx renders its own <html lang="en">
outside app/[locale]/layout.tsx, so --font-urbanist / --font-manrope are
undefined there. Previously the error page silently rendered in Times New
Roman instead of the system font stack.

Addresses review feedback from Copilot (8 inline comments) and Claude.
@Muzzamil1 Muzzamil1 merged commit cf394bf into main Apr 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants