Ensure --default-font-* and --default-mono-font-* variables respect theme customizations in JS config files (#14344)

This PR fixes an issue where variables like `--default-font-family`
wouldn't behave as expected when customizing `fontFamily.sans` or
`fontFamily.mono` in a JS config.

Because theme values added by JS config files are added as `reference`,
customizing `fontFamily.sans` means the `--font-family-sans` variable no
longer exists in the generated CSS.

The `--default-font-family` variable is set to `var(--font-family-sans)`
by default, so because that variable doesn't exist,
`--default-font-family` is effectively undefined and the browser default
font stack is used. This is unexpected because historically customizing
`fontFamily.sans` has updated your default font for your entire project.

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
Adam Wathan 2024-09-04 15:30:21 -04:00 • committed by GitHub
parent 262e99e5a9
commit 530774b186
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 555 additions and 2 deletions

View file

@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Don’t suggest named opacity modifiers in intellisense ([#14339](https://github.com/tailwindlabs/tailwindcss/pull/14339))
- Fix a crash with older Node.js versions ([#14342](https://github.com/tailwindlabs/tailwindcss/pull/14342))
- Support defining theme values as arrays of strings in JS config files ([#14343](https://github.com/tailwindlabs/tailwindcss/pull/14343))
- Ensure `--default-font-*` and `--default-mono-font-*` variables respect theme customizations in JS config files ([#14344](https://github.com/tailwindlabs/tailwindcss/pull/14344))
## [4.0.0-alpha.21] - 2024-09-02

View file

@ -2,12 +2,28 @@ import type { DesignSystem } from '../design-system'
import { resolveConfig, type ConfigFile } from './config/resolve-config'
import type { ResolvedConfig } from './config/types'
function resolveThemeValue(value: unknown, subValue: string | null = null): string | null {
if (
Array.isArray(value) &&
value.length === 2 &&
typeof value[1] === 'object' &&
typeof value[1] !== null
) {
return subValue ? (value[1][subValue] ?? null) : value[0]
} else if (Array.isArray(value) && subValue === null) {
return value.join(', ')
} else if (typeof value === 'string' && subValue === null) {
return value
}
return null
}
export function applyConfigToTheme(designSystem: DesignSystem, configs: ConfigFile[]) {
let theme = resolveConfig(designSystem, configs).theme
for (let [path, value] of themeableValues(theme)) {
let name = keyPathToCssProperty(path)
designSystem.theme.add(`--${name}`, value as any, {
isInline: true,
isReference: true,
@ -15,6 +31,56 @@ export function applyConfigToTheme(designSystem: DesignSystem, configs: ConfigFi
})
}
// If someone has updated `fontFamily.sans` or `fontFamily.mono` in a JS
// config, we need to make sure variables like `--default-font-family` and
// `--default-font-feature-settings` are updated to match those explicit
// values, because variables like `--font-family-sans` and
// `--font-family-sans--feature-settings` (which the `--default-font-*`
// variables reference) won't exist in the generated CSS.
if (Object.hasOwn(theme, 'fontFamily')) {
let options = {
isInline: true,
isReference: false,
isDefault: true,
}
// Replace `--default-font-*` with `fontFamily.sans` values
{
let fontFamily = resolveThemeValue(theme.fontFamily.sans)
if (fontFamily && designSystem.theme.hasDefault('--font-family-sans')) {
designSystem.theme.add('--default-font-family', fontFamily, options)
designSystem.theme.add(
'--default-font-feature-settings',
resolveThemeValue(theme.fontFamily.sans, 'fontFeatureSettings') ?? 'normal',
options,
)
designSystem.theme.add(
'--default-font-variation-settings',
resolveThemeValue(theme.fontFamily.sans, 'fontVariationSettings') ?? 'normal',
options,
)
}
}
// Replace `--default-mono-font-*` with `fontFamily.mono` values
{
let fontFamily = resolveThemeValue(theme.fontFamily.mono)
if (fontFamily && designSystem.theme.hasDefault('--font-family-mono')) {
designSystem.theme.add('--default-mono-font-family', 'theme(fontFamily.mono)', options)
designSystem.theme.add(
'--default-mono-font-feature-settings',
resolveThemeValue(theme.fontFamily.mono, 'fontFeatureSettings') ?? 'normal',
options,
)
designSystem.theme.add(
'--default-mono-font-variation-settings',
resolveThemeValue(theme.fontFamily.mono, 'fontVariationSettings') ?? 'normal',
options,
)
}
}
}
return theme
}

View file

@ -1,4 +1,4 @@
import { test } from 'vitest'
import { describe, test } from 'vitest'
import { compile } from '..'
import plugin from '../plugin'
@ -229,3 +229,485 @@ test('Variants in CSS overwrite variants from plugins', async ({ expect }) => {
"
`)
})
describe('default font family compatibility', () => {
test('overriding `fontFamily.sans` sets `--default-font-family`', async ({ expect }) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: 'Potato Sans',
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: Potato Sans;
--default-font-feature-settings: normal;
--default-font-variation-settings: normal;
}
.font-sans {
font-family: Potato Sans;
}
"
`)
})
test('overriding `fontFamily.sans[1].fontFeatureSettings` sets `--default-font-feature-settings`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }],
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: Potato Sans;
--default-font-feature-settings: "cv06";
--default-font-variation-settings: normal;
}
.font-sans {
font-family: Potato Sans;
font-feature-settings: "cv06";
}
"
`)
})
test('overriding `fontFamily.sans[1].fontVariationSettings` sets `--default-font-variation-settings`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }],
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: Potato Sans;
--default-font-feature-settings: normal;
--default-font-variation-settings: "XHGT" 0.7;
}
.font-sans {
font-family: Potato Sans;
font-variation-settings: "XHGT" 0.7;
}
"
`)
})
test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.sans` sets `--default-font-feature-settings` and `--default-font-variation-settings`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: [
'Potato Sans',
{ fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
],
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: Potato Sans;
--default-font-feature-settings: "cv06";
--default-font-variation-settings: "XHGT" 0.7;
}
.font-sans {
font-family: Potato Sans;
font-feature-settings: "cv06";
font-variation-settings: "XHGT" 0.7;
}
"
`)
})
test('overriding `--font-family-sans` in `@theme` without `default` preserves the original `--default-font-*` values', async ({
expect,
}) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@theme {
--font-family-sans: Sandwich Sans;
}
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: 'Potato Sans',
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
--font-family-sans: Sandwich Sans;
}
.font-sans {
font-family: var(--font-family-sans, Sandwich Sans);
}
"
`)
})
test('overriding `fontFamily.sans` in a config file with an array sets `--default-font-family`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: Inter, system-ui, sans-serif;
--default-font-feature-settings: normal;
--default-font-variation-settings: normal;
}
.font-sans {
font-family: Inter, system-ui, sans-serif;
}
"
`)
})
test('overriding `fontFamily.sans` in a config file with an unexpected type is ignored', async ({
expect,
}) => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
sans: { foo: 'bar', banana: 'sandwich' },
},
},
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root {
--default-font-family: var(--font-family-sans);
--default-font-feature-settings: var(--font-family-sans--font-feature-settings);
--default-font-variation-settings: var(--font-family-sans--font-variation-settings);
}
"
`)
})
test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async ({ expect }) => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
mono: 'Potato Mono',
},
},
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root {
--default-mono-font-family: Potato Mono;
--default-mono-font-feature-settings: normal;
--default-mono-font-variation-settings: normal;
}
.font-mono {
font-family: Potato Mono;
}
"
`)
})
test('overriding `fontFamily.mono[1].fontFeatureSettings` sets `--default-mono-font-feature-settings`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }],
},
},
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root {
--default-mono-font-family: Potato Mono;
--default-mono-font-feature-settings: "cv06";
--default-mono-font-variation-settings: normal;
}
.font-mono {
font-family: Potato Mono;
font-feature-settings: "cv06";
}
"
`)
})
test('overriding `fontFamily.mono[1].fontVariationSettings` sets `--default-mono-font-variation-settings`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }],
},
},
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root {
--default-mono-font-family: Potato Mono;
--default-mono-font-feature-settings: normal;
--default-mono-font-variation-settings: "XHGT" 0.7;
}
.font-mono {
font-family: Potato Mono;
font-variation-settings: "XHGT" 0.7;
}
"
`)
})
test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.mono` sets `--default-mono-font-feature-settings` and `--default-mono-font-variation-settings`', async ({
expect,
}) => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
mono: [
'Potato Mono',
{ fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
],
},
},
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root {
--default-mono-font-family: Potato Mono;
--default-mono-font-feature-settings: "cv06";
--default-mono-font-variation-settings: "XHGT" 0.7;
}
.font-mono {
font-family: Potato Mono;
font-feature-settings: "cv06";
font-variation-settings: "XHGT" 0.7;
}
"
`)
})
test('overriding `--font-family-mono` in `@theme` without `default` preserves the original `--default-mono-font-*` values', async ({
expect,
}) => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
@config "./config.js";
@theme {
--font-family-mono: Sandwich Mono;
}
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
mono: 'Potato Mono',
},
},
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
--font-family-mono: Sandwich Mono;
}
.font-mono {
font-family: var(--font-family-mono, Sandwich Mono);
}
"
`)
})
test('overriding `fontFamily.mono` in a config file with an unexpected type is ignored', async ({
expect,
}) => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
@config "./config.js";
@tailwind utilities;
`
let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
fontFamily: {
mono: { foo: 'bar', banana: 'sandwich' },
},
},
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root {
--default-mono-font-family: var(--font-family-mono);
--default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
}
"
`)
})
})

View file

@ -67,6 +67,10 @@ export class Theme {
return null
}
hasDefault(key: string): boolean {
return this.values.get(key)?.isDefault ?? false
}
entries() {
return this.values.entries()
}