diff --git a/packages/@react-spectrum/s2/stories/prose.mdx b/packages/@react-spectrum/s2/stories/prose.mdx
new file mode 100644
index 00000000000..d521094ea48
--- /dev/null
+++ b/packages/@react-spectrum/s2/stories/prose.mdx
@@ -0,0 +1,184 @@
+{/* Copyright 2026 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+# An Example Article to Test Prose Styles
+
+*Published March 12, 2026 · 8 min read*
+
+When teams outgrow a handful of shared CSS variables, they usually need something stronger: a **design token pipeline** that turns brand decisions into typed, versioned values every component can consume. This article walks through how one product team migrated from scattered hex codes to a single source of truth—and what they learned along the way.
+
+A token pipeline is not just a JSON file in a repo. It is the contract between design and engineering: names, scales, semantic aliases, and the build steps that keep documentation, Figma libraries, and runtime themes in sync.
+
+---
+
+## Why tokens matter
+
+Hard-coded values drift. A button might use `#0265DC` in one package and `#1473E6` in another. Tokens replace that ambiguity with **stable names** like `color-accent-default` that map to platform-specific outputs.
+
+Good token systems share a few traits:
+
+- **Semantic naming** — components reference intent, not raw values
+- **Layered aliases** — global palette → semantic roles → component tokens
+- **Automated distribution** — one change propagates to CSS, Swift, and Android
+
+> "We stopped debating hex codes in pull requests once tokens became the only thing components were allowed to import."
+> — *Maya Chen, Design Systems Lead*
+
+### From palette to semantics
+
+Start with a **global palette**: neutrals, brand hues, and status colors at fixed steps (`gray-100` through `gray-900`). Semantic tokens then *alias* those steps to roles such as `text-primary` or `border-focus`.
+
+#### Example alias chain
+
+A focus ring might resolve like this:
+
+1. `focus-ring-color` → `color-blue-800`
+2. `color-blue-800` → `#0265DC`
+3. Runtime theme overrides remap aliases without touching component code
+
+##### Naming conventions
+
+Keep names **lowercase**, **kebab-case**, and **role-oriented**. Avoid embedding the literal value in the name—`blue-600` is fine for palette steps; `primary-button-background` is better for semantics.
+
+###### Edge cases
+
+Even `h6`-level detail deserves a home: document when a token is *deprecated* versus *removed*, and link to the migration guide in the same PR that flips the alias.
+
+---
+
+## Planning the migration
+
+Before touching production components, the team audited every color, spacing, and typography value in the codebase. They grouped findings into three buckets:
+
+1. **Direct replacements** — values that already matched the new palette
+2. **Near matches** — values within one step on the scale
+3. **One-offs** — legacy colors that needed designer sign-off
+
+Nested decisions often appear in the same list:
+
+1. Choose a token format (DTCG JSON was the winner)
+ - Validate against the [Design Tokens Community Group spec](https://design-tokens.github.io/community-group/format/)
+ - Add JSON Schema in CI so bad exports fail fast
+2. Pick build tooling
+ - [Style Dictionary](https://amzn.github.io/style-dictionary/) for multi-platform output
+ - Custom transforms for Spectrum-style macro keys
+3. Roll out by package
+ - Start with primitives (`Button`, `Link`)
+ - Expand to form controls, then layout
+
+### Pre-migration checklist
+
+- [x] Inventory existing CSS variables and hard-coded literals
+- [x] Align Figma variables with token names
+- [ ] Enable lint rules blocking raw color literals in new code
+- [ ] Publish a changelog entry for breaking alias renames
+
+---
+
+## Implementation notes
+
+The build script reads `tokens.json`, resolves aliases, and emits platform files. A minimal Node entry point looks like this:
+
+```js
+import StyleDictionary from 'style-dictionary';
+
+StyleDictionary.extend({
+ source: ['tokens/**/*.json'],
+ platforms: {
+ css: {
+ transformGroup: 'css',
+ buildPath: 'dist/css/',
+ files: [{destination: 'variables.css', format: 'css/variables'}]
+ },
+ js: {
+ transformGroup: 'js',
+ buildPath: 'dist/js/',
+ files: [{destination: 'tokens.js', format: 'javascript/es6'}]
+ }
+ }
+}).buildAllPlatforms();
+```
+
+Run it after every token change:
+
+```bash
+yarn build:tokens && yarn test:tokens
+```
+
+Run this from the project root after every token change, or trigger your configured build task with ⌘ Enter.
+
+Components then import semantic values instead of literals:
+
+```tsx
+import {style} from '../style/spectrum-theme' with {type: 'macro'};
+
+export function PrimaryButton() {
+ return (
+
+ );
+}
+```
+
+Inline code appears everywhere in real docs: set `backgroundColor: 'accent-default'` in macros, grep for `#0265DC`, or wrap paths like `tokens/color/semantic.json` in backticks inside list items and headings alike.
+
+---
+
+## Token reference
+
+The table below shows a trimmed set of semantic color tokens and their light-theme values. Dark theme swaps the alias targets, not the names components use.
+
+
Token
Role
Light value
Used by
color-background-base
Page canvas
gray-50
Body, layouts
color-text-primary
Default copy
gray-900
Text, Heading
color-accent-default
Primary actions
blue-800
Button, Link
color-border-focus
Focus ring
blue-800
All interactive controls
color-negative-default
Errors
red-700
FieldError, InlineAlert
+
+For spacing, the team standardized on a **4 px grid**: `size-100` (4px), `size-200` (8px), `size-300` (12px), and so on. Typography tokens pair `font-size` with `line-height` so prose blocks like this paragraph stay readable at every breakpoint.
+
+
+
+ Figure 1. Global palette values feed semantic aliases, which components consume through typed APIs.
+
+
+---
+
+## Communication and rollout
+
+Documentation is part of the pipeline. The team shipped:
+
+- A **migration guide** linked from the monorepo README
+
+ The guide covers alias renames, codemods, and before/after examples for each package. Teams were asked to land migrations behind a feature flag when touching more than one semantic token at a time.
+
+ A short *What's changing* section at the top reduced support questions. Link to the changelog for each release so readers know which tokens moved in which version.
+- Storybook stories that render swatches from live token output
+- Office hours for product squads still on legacy variables
+
+When announcing breaking renames, be explicit. `color-brand-primary` was retired in v3; use `color-accent-default` instead. Mix **bold**, *italic*, and ***bold italic*** emphasis sparingly so warnings stand out without shouting.
+
+External references helped the team stay aligned:
+
+- [Design Tokens Community Group format](https://design-tokens.github.io/community-group/format/)
+- [Adobe Spectrum design tokens documentation](https://spectrum.adobe.com/page/design-tokens/)
+- Internal RFC: *"Semantic color roles for Express mobile"*
+
+---
+
+## Closing thoughts
+
+A token pipeline pays off when **designers edit values**, **build tools propagate them**, and **components never hard-code literals**. The upfront audit is tedious, but the alternative—endless hex drift across packages—is worse.
+
+Start small: one palette file, one build target, one component migrated end to end. Measure success by the number of raw color literals left in `git grep`, not by how pretty the JSON looks on day one.
+
+---
+
+*Questions about this guide? Open a discussion in `#design-systems` or file an issue with the `tokens` label.*
diff --git a/packages/@react-spectrum/s2/stories/prose.stories.tsx b/packages/@react-spectrum/s2/stories/prose.stories.tsx
new file mode 100644
index 00000000000..85fd5e2b060
--- /dev/null
+++ b/packages/@react-spectrum/s2/stories/prose.stories.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import type {Meta} from '@storybook/react';
+import {prose} from '../style/prose' with {type: 'macro'};
+// @ts-ignore
+import ProseExample from './prose.mdx';
+import {style} from '../style/spectrum-theme' with {type: 'macro'};
+
+const meta: Meta = {
+ tags: ['autodocs'],
+ title: 'Prose'
+};
+
+export default meta;
+
+export const Example = () => (
+
+
+
+);
diff --git a/packages/@react-spectrum/s2/style/prose.ts b/packages/@react-spectrum/s2/style/prose.ts
new file mode 100644
index 00000000000..327d6020028
--- /dev/null
+++ b/packages/@react-spectrum/s2/style/prose.ts
@@ -0,0 +1,255 @@
+import {colorToken, getToken} from './tokens';
+import {
+ colorTokenToString,
+ fontFamily,
+ fontSize,
+ fontSizeCalc,
+ fontWeight,
+ lineHeight,
+ resolveColorToken
+} from './spectrum-theme';
+import type {MacroContext} from '@parcel/macros';
+
+const marginTop = {
+ body: getToken('body-margin-multiplier') + 'em',
+ heading: getToken('heading-margin-top-multiplier') + 'em',
+ title: getToken('title-margin-top-multiplier') + 'em',
+ detail: getToken('detail-margin-top-multiplier') + 'em'
+} as const;
+
+const marginBottom = {
+ body: getToken('body-margin-multiplier') + 'em',
+ heading: getToken('heading-margin-bottom-multiplier') + 'em',
+ title: getToken('title-margin-bottom-multiplier') + 'em',
+ detail: getToken('detail-margin-bottom-multiplier') + 'em'
+} as const;
+
+export function prose(this: MacroContext | void) {
+ let rules = {
+ '.prose': font('body'),
+ h1: {
+ ...font('heading-xl'),
+ ...margin('heading')
+ },
+ h2: {
+ ...font('heading-lg'),
+ ...margin('heading')
+ },
+ h3: {
+ ...font('heading'),
+ ...margin('heading')
+ },
+ h4: {
+ ...font('heading-sm'),
+ ...margin('heading')
+ },
+ h5: {
+ ...font('heading-xs'),
+ ...margin('heading')
+ },
+ h6: {
+ ...font('heading-2xs'),
+ ...margin('heading')
+ },
+ pre: {
+ ...font('code-sm'),
+ ...margin('body'),
+ borderRadius: getToken('corner-radius-large-default'),
+ backgroundColor: colorTokenToString(
+ resolveColorToken(colorToken('background-layer-1-color'))
+ ),
+ margin: 0,
+ padding: '16px',
+ width: '100%',
+ overflow: 'auto',
+ boxSizing: 'border-box'
+ },
+ p: {
+ ...margin('body')
+ },
+ 'ul, ol': {
+ paddingInlineStart: `${24 / 16}rem`,
+ marginTop: {
+ default: marginTop.body,
+ ':is(li > *)': 0
+ },
+ marginBottom: {
+ default: marginBottom.body,
+ ':is(li > *)': 0
+ }
+ },
+ ul: {
+ listStyleType: 'disc'
+ },
+ ol: {
+ listStyleType: 'decimal'
+ },
+ 'li > p:last-child:not(:first-child)': {
+ marginBottom: marginBottom.body
+ },
+ blockquote: {
+ ...margin('body'),
+ borderStyle: 'solid',
+ borderWidth: 0,
+ borderColor: colorTokenToString(resolveColorToken(colorToken('gray-200'))),
+ borderInlineStartWidth: 2,
+ paddingInlineStart: 12,
+ marginInlineStart: 4
+ },
+ hr: {
+ marginBlock: '32px',
+ height: '2px',
+ borderRadius: '2px',
+ borderStyle: 'none',
+ backgroundColor: colorTokenToString(resolveColorToken(colorToken('gray-200')))
+ },
+ 'code:not(pre code)': {
+ ...font('code'),
+ fontSize: 'inherit',
+ backgroundColor: colorTokenToString(
+ resolveColorToken(colorToken('background-layer-1-color'))
+ ),
+ paddingInline: '4px',
+ borderWidth: 1,
+ borderColor: colorTokenToString(resolveColorToken(colorToken('gray-100'))),
+ borderStyle: 'solid',
+ borderRadius: getToken('corner-radius-small-default'),
+ whiteSpace: 'pre-wrap'
+ },
+ kbd: {
+ ...font('ui'),
+ fontSize: 'inherit',
+ paddingInline: '8px',
+ paddingBlock: '2px',
+ whiteSpace: 'nowrap',
+ backgroundColor: colorTokenToString(resolveColorToken(colorToken('gray-100'))),
+ borderRadius: getToken('corner-radius-small-default'),
+ unicodeBidi: 'plaintext'
+ },
+ a: {
+ color: {
+ default: colorTokenToString(resolveColorToken(colorToken('accent-content-color-default'))),
+ ':hover': colorTokenToString(resolveColorToken(colorToken('accent-content-color-hover'))),
+ ':active': colorTokenToString(resolveColorToken(colorToken('accent-content-color-down')))
+ },
+ textDecoration: 'underline',
+ transition: 'color 200ms'
+ },
+ ':is(h1, h2, h3, h4, h5, h6, hr) + *': {
+ marginTop: 0
+ },
+ table: {
+ ...font('ui'),
+ ...margin('body'),
+ backgroundColor: colorTokenToString(resolveColorToken(colorToken('gray-25'))),
+ borderRadius: getToken('corner-radius-medium-default'),
+ borderColor: colorTokenToString(resolveColorToken(colorToken('gray-300'))),
+ borderWidth: '1px',
+ borderStyle: 'solid',
+ overflow: 'hidden',
+ borderSpacing: 0,
+ width: 'full'
+ },
+ thead: {
+ backgroundColor: colorTokenToString(resolveColorToken(colorToken('gray-75'))),
+ borderTopRadius: 'default'
+ },
+ th: {
+ paddingInline: '16px',
+ textAlign: 'start',
+ fontWeight: 'bold',
+ borderColor: colorTokenToString(resolveColorToken(colorToken('gray-300'))),
+ borderWidth: 0,
+ borderBottomWidth: 1,
+ borderStyle: 'solid',
+ height: '32px',
+ boxSizing: 'border-box'
+ },
+ td: {
+ paddingInline: '16px',
+ paddingBlock: '4px',
+ borderWidth: 0,
+ borderBottomWidth: {
+ default: '1px',
+ ':is(tbody:last-child > tr:last-child > *)': 0
+ },
+ borderStyle: 'solid',
+ borderColor: colorTokenToString(resolveColorToken(colorToken('gray-300'))),
+ boxSizing: 'border-box'
+ },
+ 'img, video': {
+ maxWidth: '100%'
+ },
+ figure: {
+ ...margin('body'),
+ marginInline: 0
+ },
+ figcaption: {
+ ...font('body-sm'),
+ textAlign: 'center'
+ }
+ };
+
+ let css = '';
+ for (let key in rules) {
+ let selector = key === '.prose' ? '.prose' : `.prose ${key}`;
+ css += `${selector} {\n`;
+ let properties = rules[key];
+ for (let property in properties) {
+ let value = properties[property];
+ let prop = property.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`);
+ if (typeof value === 'object') {
+ if (value.default) {
+ css += ` ${prop}: ${value.default};\n`;
+ }
+ for (let condition in value) {
+ // eslint-disable-next-line
+ if (condition === 'default') {
+ continue;
+ }
+ css += ` ${condition.startsWith(':') ? '&' : ''}${condition} { ${prop}: ${value[condition]}; }\n`;
+ }
+ } else {
+ css += ` ${prop}: ${value};\n`;
+ }
+ }
+
+ css += `}\n\n`;
+ }
+
+ this?.addAsset({
+ type: 'css',
+ content: css
+ });
+
+ return 'prose';
+}
+
+function font(value: keyof typeof fontSize) {
+ let type = value.split('-')[0];
+ let size = fontSize[value];
+ return {
+ fontFamily: fontFamily[type === 'code' ? 'code' : 'sans'],
+ '--fs': `pow(1.125, ${size})`,
+ fontSize: `round(${fontSizeCalc} / 16 * 1rem, 1px)`,
+ fontWeight:
+ fontWeight[type === 'heading' || type === 'title' || type === 'detail' ? type : 'normal'],
+ lineHeight: lineHeight[type],
+ color: colorTokenToString(
+ resolveColorToken(colorToken(type === 'ui' ? 'body-color' : (`${type}-color` as any)))
+ )
+ };
+}
+
+function margin(value: keyof typeof marginTop) {
+ return {
+ marginTop: {
+ default: marginTop[value],
+ ':first-child': 0
+ },
+ marginBottom: {
+ default: marginBottom[value],
+ ':last-child': 0
+ }
+ };
+}
diff --git a/packages/@react-spectrum/s2/style/spectrum-theme.ts b/packages/@react-spectrum/s2/style/spectrum-theme.ts
index 9bd80ef108a..3690d893241 100644
--- a/packages/@react-spectrum/s2/style/spectrum-theme.ts
+++ b/packages/@react-spectrum/s2/style/spectrum-theme.ts
@@ -119,7 +119,7 @@ const baseColors = {
};
// Resolves a color to its most basic form, following all aliases.
-function resolveColorToken(token: string | ColorToken | ColorRef): ColorToken {
+export function resolveColorToken(token: string | ColorToken | ColorRef): ColorToken {
if (typeof token === 'string') {
return {
type: 'color',
@@ -150,7 +150,7 @@ function resolveColorToken(token: string | ColorToken | ColorRef): ColorToken {
};
}
-function colorTokenToString(token: ColorToken, opacity?: string | number) {
+export function colorTokenToString(token: ColorToken, opacity?: string | number) {
let result =
token.light === token.dark ? token.light : `light-dark(${token.light}, ${token.dark})`;
if (opacity) {
@@ -577,7 +577,9 @@ const timingFunction = {
let durationValue = (value: number | string) => (typeof value === 'number' ? value + 'ms' : value);
const fontWeightBase = {
- normal: '400',
+ normal: {
+ default: '400'
+ },
medium: {
default: '500'
},
@@ -589,32 +591,31 @@ const fontWeightBase = {
default: '800',
':lang(ja, ko, zh)': '700' // Adobe Clean Han uses 700 as the extra bold weight.
},
- black: '900'
+ black: {
+ default: '900'
+ }
} as const;
-const fontWeight = {
+export const fontWeight = {
...fontWeightBase,
heading: {
- default:
- fontWeightBase[getToken('heading-sans-serif-font-weight') as keyof typeof fontWeightBase],
+ ...fontWeightBase[getToken('heading-sans-serif-font-weight') as keyof typeof fontWeightBase],
':lang(ja, ko, zh, zh-Hant, zh-Hans)':
fontWeightBase[getToken('heading-cjk-font-weight') as keyof typeof fontWeightBase]
},
title: {
- default:
- fontWeightBase[getToken('title-sans-serif-font-weight') as keyof typeof fontWeightBase],
+ ...fontWeightBase[getToken('title-sans-serif-font-weight') as keyof typeof fontWeightBase],
':lang(ja, ko, zh, zh-Hant, zh-Hans)':
fontWeightBase[getToken('title-cjk-font-weight') as keyof typeof fontWeightBase]
},
detail: {
- default:
- fontWeightBase[getToken('detail-sans-serif-font-weight') as keyof typeof fontWeightBase],
+ ...fontWeightBase[getToken('detail-sans-serif-font-weight') as keyof typeof fontWeightBase],
':lang(ja, ko, zh, zh-Hant, zh-Hans)':
fontWeightBase[getToken('detail-cjk-font-weight') as keyof typeof fontWeightBase]
}
} as const;
-const i18nFonts = {
+export const i18nFonts = {
':lang(ar)': 'adobe-clean-arabic, myriad-arabic, ui-sans-serif, system-ui, sans-serif',
':lang(he)': 'adobe-clean-hebrew, myriad-hebrew, ui-sans-serif, system-ui, sans-serif',
':lang(ja)':
@@ -632,7 +633,7 @@ const i18nFonts = {
"adobe-clean-han-simplified-c, source-han-simplified-c, 'SimSun', 'Heiti SC Light', sans-serif"
} as const;
-const fontSize = {
+export const fontSize = {
// The default font size scale is for use within UI components.
'ui-xs': fontSizeToken('font-size-50'),
'ui-sm': fontSizeToken('font-size-75'),
@@ -681,14 +682,58 @@ const fontSize = {
'code-xl': fontSizeToken('code-size-xl')
} as const;
+export const fontFamily = {
+ sans: {
+ default:
+ 'var(--s2-font-family-sans, adobe-clean-spectrum-vf), adobe-clean-variable, adobe-clean, ui-sans-serif, system-ui, sans-serif',
+ ...i18nFonts
+ },
+ serif: {
+ default:
+ 'var(--s2-font-family-serif, adobe-clean-spectrum-srf-vf), adobe-clean-serif, "Source Serif", Georgia, serif',
+ ...i18nFonts
+ },
+ code: 'source-code-pro, "Source Code Pro", Monaco, monospace'
+} as const;
+
// Line heights linearly interpolate between 1.3 and 1.15 for font sizes between 10 and 32, rounded to the nearest 2px.
// Text above 32px always has a line height of 1.15.
-const fontSizeCalc = 'var(--s2-font-size-base, 14) * var(--fs)';
+export const fontSizeCalc = 'var(--s2-font-size-base, 14) * var(--fs)';
const minFontScale = 1.15;
const maxFontScale = 1.3;
const minFontSize = 10;
const maxFontSize = 32;
const lineHeightCalc = `round(1em * (${minFontScale} + (1 - ((min(${maxFontSize}, ${fontSizeCalc}) - ${minFontSize})) / ${maxFontSize - minFontSize}) * ${(maxFontScale - minFontScale).toFixed(2)}), 2px)`;
+export const lineHeight = {
+ // See https://spectrum.corp.adobe.com/page/typography/#Line-height
+ ui: {
+ // Calculate line-height based on font size.
+ default: lineHeightCalc,
+ // CJK fonts use a larger line-height.
+ ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('line-height-200')
+ },
+ heading: {
+ default: lineHeightCalc,
+ ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('heading-cjk-line-height')
+ },
+ title: {
+ default: lineHeightCalc,
+ ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('title-cjk-line-height')
+ },
+ body: {
+ // Body text uses spacious line height, 1.5 for all font sizes.
+ default: getToken('body-line-height'),
+ ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('body-cjk-line-height')
+ },
+ detail: {
+ default: lineHeightCalc,
+ ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('detail-cjk-line-height')
+ },
+ code: {
+ default: getToken('code-line-height'),
+ ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('code-cjk-line-height')
+ }
+} as const;
export const style = createTheme({
properties: {
@@ -928,19 +973,7 @@ export const style = createTheme({
),
// text
- fontFamily: {
- sans: {
- default:
- 'var(--s2-font-family-sans, adobe-clean-spectrum-vf), adobe-clean-variable, adobe-clean, ui-sans-serif, system-ui, sans-serif',
- ...i18nFonts
- },
- serif: {
- default:
- 'var(--s2-font-family-serif, adobe-clean-spectrum-srf-vf), adobe-clean-serif, "Source Serif", Georgia, serif',
- ...i18nFonts
- },
- code: 'source-code-pro, "Source Code Pro", Monaco, monospace'
- },
+ fontFamily,
fontSize: new ExpandedProperty(
['--fs', 'fontSize'],
value => {
@@ -965,36 +998,7 @@ export const style = createTheme({
},
fontWeight
),
- lineHeight: {
- // See https://spectrum.corp.adobe.com/page/typography/#Line-height
- ui: {
- // Calculate line-height based on font size.
- default: lineHeightCalc,
- // CJK fonts use a larger line-height.
- ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('line-height-200')
- },
- heading: {
- default: lineHeightCalc,
- ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('heading-cjk-line-height')
- },
- title: {
- default: lineHeightCalc,
- ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('title-cjk-line-height')
- },
- body: {
- // Body text uses spacious line height, 1.5 for all font sizes.
- default: getToken('body-line-height'),
- ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('body-cjk-line-height')
- },
- detail: {
- default: lineHeightCalc,
- ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('detail-cjk-line-height')
- },
- code: {
- default: getToken('code-line-height'),
- ':lang(ja, ko, zh, zh-Hant, zh-Hans, zh-CN, zh-SG)': getToken('code-cjk-line-height')
- }
- },
+ lineHeight,
listStyleType: ['none', 'disc', 'decimal'] as const,
listStylePosition: ['inside', 'outside'] as const,
textTransform: ['uppercase', 'lowercase', 'capitalize', 'none'] as const,