feat: add geist fonts (#9502)

This commit is contained in:
shadcn
2026-01-31 14:52:43 +04:00
committed by GitHub
parent 3192a3db55
commit 2bf55c9133
40 changed files with 584 additions and 75 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
properly detect already installed fonts

View File

@@ -59,7 +59,7 @@ export function FontPicker({
anchor={isMobile ? anchorRef : undefined}
side={isMobile ? "top" : "right"}
align={isMobile ? "center" : "start"}
className="max-h-80 md:w-72"
className="max-h-96 md:w-72"
>
<PickerRadioGroup
value={currentFont?.value}

View File

@@ -76,7 +76,7 @@ export function ThemePicker({
anchor={isMobile ? anchorRef : undefined}
side={isMobile ? "top" : "right"}
align={isMobile ? "center" : "start"}
className="max-h-96"
className="max-h-[23rem]"
>
<PickerRadioGroup
value={currentTheme?.name}

View File

@@ -38,15 +38,15 @@ const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono",
})
// const geistSans = Geist({
// subsets: ["latin"],
// variable: "--font-geist-sans",
// })
const geistSans = Geist({
subsets: ["latin"],
variable: "--font-geist-sans",
})
// const geistMono = Geist_Mono({
// subsets: ["latin"],
// variable: "--font-geist-mono",
// })
const geistMono = Geist_Mono({
subsets: ["latin"],
variable: "--font-geist-mono",
})
const roboto = Roboto({
subsets: ["latin"],
@@ -74,12 +74,12 @@ const outfit = Outfit({
})
export const FONTS = [
// {
// name: "Geist Sans",
// value: "geist",
// font: geistSans,
// type: "sans",
// },
{
name: "Geist",
value: "geist",
font: geistSans,
type: "sans",
},
{
name: "Inter",
value: "inter",
@@ -134,18 +134,18 @@ export const FONTS = [
font: outfit,
type: "sans",
},
{
name: "Geist Mono",
value: "geist-mono",
font: geistMono,
type: "mono",
},
{
name: "JetBrains Mono",
value: "jetbrains-mono",
font: jetbrainsMono,
type: "mono",
},
// {
// name: "Geist Mono",
// value: "geist-mono",
// font: geistMono,
// type: "mono",
// },
] as const
export type Font = (typeof FONTS)[number]

View File

@@ -19,13 +19,13 @@
{
"name": "radix-nova",
"title": "Nova (Radix)",
"description": "Nova / Hugeicons / Inter",
"description": "Nova / Hugeicons / Geist",
"base": "radix",
"style": "nova",
"baseColor": "neutral",
"theme": "neutral",
"iconLibrary": "hugeicons",
"font": "inter",
"font": "geist",
"item": "Item",
"rtl": false,
"menuAccent": "subtle",
@@ -83,13 +83,13 @@
{
"name": "base-nova",
"title": "Nova (Base)",
"description": "Nova / Hugeicons / Inter",
"description": "Nova / Hugeicons / Geist",
"base": "base",
"style": "nova",
"baseColor": "neutral",
"theme": "neutral",
"iconLibrary": "hugeicons",
"font": "inter",
"font": "geist",
"item": "Item",
"rtl": false,
"menuAccent": "subtle",

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1870,8 +1870,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2016,6 +2016,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1870,8 +1870,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2016,6 +2016,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1870,8 +1870,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2016,6 +2016,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1870,8 +1870,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2016,6 +2016,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1870,8 +1870,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2016,6 +2016,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1863,8 +1863,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2009,6 +2009,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1863,8 +1863,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2009,6 +2009,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1863,8 +1863,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2009,6 +2009,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1863,8 +1863,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2009,6 +2009,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
"provider": "google",
"import": "Geist",
"variable": "--font-sans",
"subsets": [
"latin"
]
}
}

View File

@@ -1863,8 +1863,8 @@
"type": "registry:hook"
},
{
"name": "font-geist-sans",
"title": "Geist Sans",
"name": "font-geist",
"title": "Geist",
"type": "registry:font",
"font": {
"family": "'Geist Variable', sans-serif",
@@ -2009,6 +2009,20 @@
"latin"
]
}
},
{
"name": "font-geist-mono",
"title": "Geist Mono",
"type": "registry:font",
"font": {
"family": "'Geist Mono Variable', monospace",
"provider": "google",
"import": "Geist_Mono",
"variable": "--font-mono",
"subsets": [
"latin"
]
}
}
]
}

View File

@@ -151,13 +151,13 @@ export const PRESETS: Preset[] = [
{
name: "radix-nova",
title: "Nova (Radix)",
description: "Nova / Hugeicons / Inter",
description: "Nova / Hugeicons / Geist",
base: "radix",
style: "nova",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "hugeicons",
font: "inter",
font: "geist",
item: "Item",
rtl: false,
menuAccent: "subtle",
@@ -216,13 +216,13 @@ export const PRESETS: Preset[] = [
{
name: "base-nova",
title: "Nova (Base)",
description: "Nova / Hugeicons / Inter",
description: "Nova / Hugeicons / Geist",
base: "base",
style: "nova",
baseColor: "neutral",
theme: "neutral",
iconLibrary: "hugeicons",
font: "inter",
font: "geist",
item: "Item",
rtl: false,
menuAccent: "subtle",

View File

@@ -2,8 +2,8 @@ import { type RegistryItem } from "shadcn/schema"
export const fonts = [
{
name: "font-geist-sans",
title: "Geist Sans",
name: "font-geist",
title: "Geist",
type: "registry:font",
font: {
family: "'Geist Variable', sans-serif",
@@ -131,16 +131,16 @@ export const fonts = [
import: "JetBrains_Mono",
},
},
// {
// name: "font-geist-mono",
// title: "Geist Mono",
// type: "registry:font",
// font: {
// family: "'Geist Mono Variable', monospace",
// provider: "google",
// variable: "--font-sans",
// subsets: ["latin"],
// import: "Geist_Mono",
// },
// },
{
name: "font-geist-mono",
title: "Geist Mono",
type: "registry:font",
font: {
family: "'Geist Mono Variable', monospace",
provider: "google",
variable: "--font-mono",
subsets: ["latin"],
import: "Geist_Mono",
},
},
] satisfies RegistryItem[]

View File

@@ -1,6 +1,7 @@
import path from "path"
import { getPreset, getPresets, getRegistryItems } from "@/src/registry/api"
import { configWithDefaults } from "@/src/registry/config"
import { REGISTRY_URL } from "@/src/registry/constants"
import { clearRegistryContext } from "@/src/registry/context"
import { isUrl } from "@/src/registry/utils"
import { Preset } from "@/src/schema"
@@ -18,7 +19,7 @@ import validateProjectName from "validate-npm-package-name"
import { initOptionsSchema, runInit } from "./init"
const SHADCN_URL = "https://ui.shadcn.com"
const SHADCN_URL = REGISTRY_URL.replace(/\/r\/?$/, "")
const CREATE_TEMPLATES = {
next: "Next.js",

View File

@@ -386,7 +386,7 @@ export default function RootLayout({
`)
})
it("should not duplicate existing font import", async () => {
it("should skip font entirely if already imported", async () => {
const input = `
import { Inter } from "next/font/google"
@@ -420,6 +420,7 @@ export default function RootLayout({
const result = await transformLayoutFonts(input, fonts, mockConfig)
// Font is already imported, so the layout should remain unchanged.
expect(result).toMatchInlineSnapshot(`
"
import { Inter } from "next/font/google"
@@ -432,7 +433,7 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.variable}>
<html lang="en">
<body className={inter.variable}>{children}</body>
</html>
)
@@ -441,6 +442,64 @@ export default function RootLayout({
`)
})
it("should skip Geist font if already imported (create-next-app scenario)", async () => {
// This simulates a fresh create-next-app project with Geist already set up.
const input = `
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={\`\${geistSans.variable} \${geistMono.variable} antialiased\`}
>
{children}
</body>
</html>
);
}
`
const fonts = [
{
name: "font-geist",
type: "registry:font" as const,
font: {
family: "'Geist Variable', sans-serif",
provider: "google" as const,
import: "Geist",
variable: "--font-sans",
subsets: ["latin"],
},
},
]
const result = await transformLayoutFonts(input, fonts, mockConfig)
// Geist is already imported, so the layout should remain unchanged.
expect(result).toBe(input)
})
it("should add to existing next/font/google import", async () => {
const input = `
import { Roboto } from "next/font/google"

View File

@@ -185,26 +185,24 @@ export async function transformLayoutFonts(
continue
}
// Generate a variable name from the import (e.g., "Inter" -> "inter", "Geist_Mono" -> "geistMono").
const varName = toCamelCase(importName)
// Check if import already exists.
const existingImport = sourceFile.getImportDeclaration((decl) => {
const moduleSpecifier = decl.getModuleSpecifierValue()
return moduleSpecifier === "next/font/google"
})
// Build font options.
const fontOptions = buildFontOptions(font)
if (existingImport) {
// Check if this specific font is already imported.
const namedImports = existingImport.getNamedImports()
const hasImport = namedImports.some((imp) => imp.getName() === importName)
if (!hasImport) {
existingImport.addNamedImport(importName)
if (hasImport) {
// Font is already imported - skip this font entirely.
// Assume the user already has it set up correctly.
continue
}
existingImport.addNamedImport(importName)
} else {
// Add new import.
sourceFile.addImportDeclaration({
@@ -213,6 +211,12 @@ export async function transformLayoutFonts(
})
}
// Generate a variable name from the import (e.g., "Inter" -> "inter", "Geist_Mono" -> "geistMono").
const varName = toCamelCase(importName)
// Build font options.
const fontOptions = buildFontOptions(font)
// Check if variable declaration already exists with same variable CSS property.
const existingVarDecl = findFontVariableDeclaration(
sourceFile,