Skip to content

feat: add Noto Sans as default admin UI font with multi-script support#600

Merged
ascorbic merged 2 commits intomainfrom
feat/admin-fonts
Apr 16, 2026
Merged

feat: add Noto Sans as default admin UI font with multi-script support#600
ascorbic merged 2 commits intomainfrom
feat/admin-fonts

Conversation

@ascorbic
Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic commented Apr 16, 2026

What does this PR do?

Adds Noto Sans as the default font for the admin UI, loaded via the Astro Font API. Fonts are downloaded from Google at build time and self-hosted, so there are no runtime CDN requests. Users can add additional writing systems (Arabic, CJK, Hebrew, etc.) via a simple config option.

Type of change

How it works

  • A custom Astro font provider (font-provider.ts) wraps the Google provider to resolve multiple Noto Sans script families under a single font-family name. This is needed because Astro's Font API warns and overwrites when different font names share a cssVariable. The custom provider merges all faces so they stack via unicode-range.
  • The integration injects Noto Sans with all its native subsets (Latin, Cyrillic, Greek, Devanagari, Vietnamese) via updateConfig in astro:config:setup.
  • The admin CSS overrides Tailwind's --font-sans to use var(--font-emdash, <system fallback>).
  • The <Font cssVariable="--font-emdash" /> component is added to the admin route head (without preload to avoid downloading all subsets eagerly).

User-facing API

// Default: Noto Sans with Latin, Cyrillic, Greek, Devanagari, Vietnamese
emdash()

// Add Arabic
emdash({ fonts: { scripts: ["arabic"] } })

// Add multiple scripts
emdash({ fonts: { scripts: ["arabic", "japanese", "korean"] } })

// System fonts only
emdash({ fonts: false })

25 scripts available: arabic, armenian, bengali, chinese-simplified, chinese-traditional, chinese-hongkong, devanagari, ethiopic, georgian, gujarati, gurmukhi, hebrew, japanese, kannada, khmer, korean, lao, malayalam, myanmar, oriya, sinhala, tamil, telugu, thai, tibetan.

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation and pnpm locale:extract has been run (if applicable)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: Feat: Configurable admin font #588

AI-generated code disclosure

  • This PR includes AI-generated code

Screenshots / test output

All 2375 core tests, 549 admin tests, and all other package tests pass.

Copilot AI review requested due to automatic review settings April 16, 2026 10:40
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 16, 2026

🦋 Changeset detected

Latest commit: ca778ba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/admin Minor
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 16, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground ca778ba Apr 16 2026, 10:54 AM

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 16, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@600

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@600

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@600

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@600

emdash

npm i https://pkg.pr.new/emdash@600

create-emdash

npm i https://pkg.pr.new/create-emdash@600

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@600

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@600

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@600

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@600

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@600

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@600

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@600

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@600

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@600

commit: ca778ba

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds self-hosted Noto Sans as the default admin UI font via Astro’s Font API, with an EmDash config option to include additional writing systems.

Changes:

  • Injects a default fonts entry (--font-emdash) from the EmDash Astro integration, with optional extra script families via fonts.scripts and opt-out via fonts: false.
  • Adds a custom notoSans font provider that merges multiple Noto Sans script families under one logical font (via unicode-range stacking).
  • Updates admin UI CSS and docs to reference the new font configuration and CSS variable.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/core/src/astro/routes/admin.astro Adds <Font cssVariable="--font-emdash" /> and uses --font-emdash in the boot loader font stack.
packages/core/src/astro/integration/runtime.ts Introduces EmDashConfig.fonts config API with script support / opt-out.
packages/core/src/astro/integration/index.ts Injects default Noto Sans fonts into Astro config via updateConfig.
packages/core/src/astro/integration/font-provider.ts New custom provider that wraps Google Fonts provider and merges script faces.
packages/admin/src/styles.css Overrides Tailwind --font-sans to use --font-emdash with system/emoji fallbacks.
docs/src/content/docs/reference/configuration.mdx Documents the new fonts config option and script list.
.changeset/wet-kings-bake.md Adds changeset for the new default admin font feature.

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

Comment on lines +116 to +149
async init(context) {
await googleProvider.init?.(context);
},
async resolveFont(resolveFontOptions) {
const { weights, styles, formats } = resolveFontOptions;

// Resolve the base Noto Sans (Latin, Cyrillic, Greek, etc.)
const base = await googleProvider.resolveFont(resolveFontOptions);
const baseFonts = base?.fonts ?? [];

if (!options?.scripts?.length) {
return base;
}

// Collect subset names already covered by the base font so we
// can filter out duplicate faces from extra script families.
// e.g. Noto Sans Arabic includes latin/latin-ext faces that
// would otherwise override the base Noto Sans latin faces.
const baseSubsets = new Set(baseFonts.map((f) => f.meta?.subset).filter(Boolean));

// Resolve additional script families
const extraFonts = await Promise.all(
options.scripts.map(async (script) => {
const family = NOTO_SCRIPT_FAMILIES[script];
if (!family) {
// Silently skip subset names that are already covered
// by the base Noto Sans font (latin, cyrillic, etc.)
if (ALL_GOOGLE_SUBSETS.includes(script)) {
return undefined;
}
console.warn(
`[emdash] Unknown Noto Sans script "${script}". ` +
`Available: ${Object.keys(NOTO_SCRIPT_FAMILIES).join(", ")}`,
);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This provider logs unknown scripts with console.warn, which bypasses Astro’s integration logger formatting/filtering. Since init(context) is available, consider capturing context.logger (if present) and using logger.warn(...) instead (fall back to console.warn only if no logger is provided).

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +161
familyName: family,
weights,
styles,
// Pass all known subset names so the unifont provider
// doesn't filter out any faces. Each script family
// only returns faces for its own subsets anyway.
subsets: ALL_GOOGLE_SUBSETS,
formats,
options: undefined,
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

When resolving extra script families, the call to googleProvider.resolveFont explicitly sets options: undefined, while the base call forwards the full resolveFontOptions (including any provider options Astro passes through). This can lead to base and extra faces being generated with different Google provider options (e.g. display strategy) for the same logical font. Forward resolveFontOptions.options (or spread resolveFontOptions and override only familyName/subsets) so all resolved families use consistent options.

Suggested change
familyName: family,
weights,
styles,
// Pass all known subset names so the unifont provider
// doesn't filter out any faces. Each script family
// only returns faces for its own subsets anyway.
subsets: ALL_GOOGLE_SUBSETS,
formats,
options: undefined,
...resolveFontOptions,
familyName: family,
// Pass all known subset names so the unifont provider
// doesn't filter out any faces. Each script family
// only returns faces for its own subsets anyway.
subsets: ALL_GOOGLE_SUBSETS,

Copilot uses AI. Check for mistakes.
Comment on lines +222 to +252
const emdashFonts =
fontsConfig === false
? []
: [
{
provider: notoSans({
scripts: fontsConfig?.scripts,
}),
name: "Noto Sans",
cssVariable: "--font-emdash",
weights: ["100 900" as const],
styles: ["normal" as const, "italic" as const],
subsets: [
"latin" as const,
"latin-ext" as const,
"cyrillic" as const,
"cyrillic-ext" as const,
"devanagari" as const,
"greek" as const,
"greek-ext" as const,
"vietnamese" as const,
],
fallbacks: ["ui-sans-serif", "system-ui", "sans-serif"],
},
];

updateConfig({
security: securityConfig,
// fonts is a valid AstroConfig key but may not be in the
// type definition for the minimum supported Astro version
...({ fonts: emdashFonts } as Record<string, unknown>),
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

updateConfig sets the top-level fonts config to emdashFonts (or [] when fonts: false) without incorporating any existing astroConfig.fonts. This risks overriding user/site font configuration (e.g. templates in this repo define fonts: [...] in astro.config.mjs) and fonts: false would unexpectedly wipe those entries. Consider merging instead (append EmDash’s --font-emdash entry to any existing astroConfig.fonts, and when fonts === false avoid touching fonts at all).

Suggested change
const emdashFonts =
fontsConfig === false
? []
: [
{
provider: notoSans({
scripts: fontsConfig?.scripts,
}),
name: "Noto Sans",
cssVariable: "--font-emdash",
weights: ["100 900" as const],
styles: ["normal" as const, "italic" as const],
subsets: [
"latin" as const,
"latin-ext" as const,
"cyrillic" as const,
"cyrillic-ext" as const,
"devanagari" as const,
"greek" as const,
"greek-ext" as const,
"vietnamese" as const,
],
fallbacks: ["ui-sans-serif", "system-ui", "sans-serif"],
},
];
updateConfig({
security: securityConfig,
// fonts is a valid AstroConfig key but may not be in the
// type definition for the minimum supported Astro version
...({ fonts: emdashFonts } as Record<string, unknown>),
const emdashFont =
fontsConfig === false
? undefined
: {
provider: notoSans({
scripts: fontsConfig?.scripts,
}),
name: "Noto Sans",
cssVariable: "--font-emdash",
weights: ["100 900" as const],
styles: ["normal" as const, "italic" as const],
subsets: [
"latin" as const,
"latin-ext" as const,
"cyrillic" as const,
"cyrillic-ext" as const,
"devanagari" as const,
"greek" as const,
"greek-ext" as const,
"vietnamese" as const,
],
fallbacks: ["ui-sans-serif", "system-ui", "sans-serif"],
};
const existingFonts = Array.isArray((astroConfig as Record<string, unknown>).fonts)
? ((astroConfig as Record<string, unknown>).fonts as unknown[])
: [];
const fontsUpdate =
emdashFont === undefined
? {}
: ({
fonts: [...existingFonts, emdashFont],
} as Record<string, unknown>);
updateConfig({
security: securityConfig,
// fonts is a valid AstroConfig key but may not be in the
// type definition for the minimum supported Astro version
...fontsUpdate,

Copilot uses AI. Check for mistakes.
Comment thread packages/admin/src/styles.css Outdated
Comment on lines +79 to +80
* Users can add extra script coverage (Arabic, CJK, etc.) by adding
* their own fonts targeting --font-emdash in astro.config.
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The comment suggests users can "add extra script coverage ... by adding their own fonts targeting --font-emdash in astro.config", but the integration’s approach (and font-provider.ts header) indicates Astro warns/overwrites when multiple font entries share the same cssVariable. This guidance is likely to mislead users into a non-working setup. Update the comment to point to emdash({ fonts: { scripts: [...] } }), or to disabling EmDash fonts (fonts:false) before configuring their own font entry/CSS variable.

Suggested change
* Users can add extra script coverage (Arabic, CJK, etc.) by adding
* their own fonts targeting --font-emdash in astro.config.
* To add extra script coverage (Arabic, CJK, etc.), configure EmDash with
* emdash({ fonts: { scripts: [...] } }) rather than adding another font entry
* that targets --font-emdash. If you need to supply your own font entry and
* CSS variable instead, disable EmDash-managed fonts first with fonts: false.

Copilot uses AI. Check for mistakes.
@ascorbic ascorbic merged commit 9295cc1 into main Apr 16, 2026
29 checks passed
@ascorbic ascorbic deleted the feat/admin-fonts branch April 16, 2026 11:05
@emdashbot emdashbot bot mentioned this pull request Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants