Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/quiet-roles-align.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Add typography role tokens. A role sets font size, line height, font weight, and
<p className="text-body-md text-muted-foreground">Orders sent to suppliers this month.</p>
```

Each role reads four CSS variables named `--app-shell-type-<role>-<size|line-height|weight|letter-spacing>`, which you can override on `:root`. AppShell's `cn()` now keeps a role together with a text color class, for example in `className="text-label-md text-muted-foreground"` passed to an AppShell component. If your app has its own `tailwind-merge` setup, see the Typography section of the styling guide.
Each role reads four CSS variables named `--type-<role>-<size|line-height|weight|letter-spacing>`, which you can override on `:root`. AppShell's `cn()` now keeps a role together with a text color class, for example in `className="text-label-md text-muted-foreground"` passed to an AppShell component. If your app has its own `tailwind-merge` setup, see the Typography section of the styling guide.
2 changes: 1 addition & 1 deletion decisions/typography-roles.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Each role is a named set of four values: size, line height, weight, and letter s

### Two layers, as for colors

- `themes/default.css` holds the values as `--app-shell-type-<role>-<size|line-height|weight|letter-spacing>` on `:root`. A palette or an app can override them. The `--app-shell-` prefix follows the existing `--app-shell-font-sans` override variable and keeps these `:root` variables apart from variables defined by the app or other libraries.
- `themes/default.css` holds the values as `--type-<role>-<size|line-height|weight|letter-spacing>` on `:root`. A palette or an app can override them. They have no `--app-shell-` prefix, like the colour roles and `--semantic-shadow-*`. `--app-shell-font-sans` needs the prefix because `--font-*` is a Tailwind theme namespace; `--type-*` is not, and the bridge writes `--text-<role>`, so the names do not clash with the app's Tailwind.
- `theme.bridge.css` maps them to Tailwind with `--text-<role>` and its `--line-height`, `--font-weight`, and `--letter-spacing` suffixes, inside the existing `@theme inline`.

`@theme inline` writes literal values into the utility. If the values sat in the bridge, they could not be overridden on `:root`. So the bridge holds only `var()` references.
Expand Down
6 changes: 3 additions & 3 deletions docs-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1304,9 +1304,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
"outline": "e63d837190e71ff1",
"outline": "208932a187e21671",
"snapshot": null,
"outputMd": "112b43c697d30369",
"outputMd": "5a624c507ee93e09",
"examples": "4d19f4f688e0f0ac"
}
},
Expand Down Expand Up @@ -1676,7 +1676,7 @@
"packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165",
"packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "8034c7f885994b29",
"packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "e342ef02dc2b9b86",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "112b43c697d30369",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "5a624c507ee93e09",
"packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b",
"packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0",
"packages/core/skills/app-shell-patterns/references/components/ai-chat.md": "824d1aa36bb7f194",
Expand Down
6 changes: 3 additions & 3 deletions docs-src/concepts/styling-theming.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,12 @@ On an `h1` to `h6` element, the `cream` and `bloom` palettes replace the role's

Always use `tabular-nums` for numbers that stack in a column — without it, digits jitter between rows.

**Change a role.** Each role is four CSS variables named `--app-shell-type-<role>-<size|line-height|weight|letter-spacing>`. Override them on `:root` to change a role for the whole app. Typography has no dark variant, so one override applies to light and dark mode.
**Change a role.** Each role is four CSS variables named `--type-<role>-<size|line-height|weight|letter-spacing>`. Override them on `:root` to change a role for the whole app. Typography has no dark variant, so one override applies to light and dark mode.

```css
:root {
--app-shell-type-heading-lg-size: 1.75rem;
--app-shell-type-heading-lg-line-height: calc(36 / 28);
--type-heading-lg-size: 1.75rem;
--type-heading-lg-line-height: calc(36 / 28);
}
```

Expand Down
6 changes: 3 additions & 3 deletions docs/concepts/styling-theming.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions packages/core/src/assets/theme-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,20 @@ describe("theme bridge", () => {

describe("typography roles", () => {
const slots = ["size", "line-height", "weight", "letter-spacing"] as const;
/** `--app-shell-type-<role>-<slot>` variables in the default palette, grouped by role. */
/** `--type-<role>-<slot>` variables in the default palette, grouped by role. */
const defined = new Map<string, Map<string, string>>();
for (const m of defaultTheme.matchAll(
/^\s*--app-shell-type-([a-z-]+)-(size|line-height|weight|letter-spacing):\s*([^;]+);/gm,
/^\s*--type-([a-z-]+)-(size|line-height|weight|letter-spacing):\s*([^;]+);/gm,
)) {
const role = m[1];
defined.set(role, (defined.get(role) ?? new Map()).set(m[2], m[3].trim()));
}
/** Bridge entries `--text-<role>[--<suffix>]: var(--app-shell-type-...)`, keyed by property name. */
/** Bridge entries `--text-<role>[--<suffix>]: var(--type-...)`, keyed by property name. */
const bridgedText = new Map(
[...bridge.matchAll(/^\s*(--text-[a-z-]+):\s*var\((--app-shell-type-[a-z-]+)\);/gm)].map(
(m) => [m[1], m[2]],
),
[...bridge.matchAll(/^\s*(--text-[a-z-]+):\s*var\((--type-[a-z-]+)\);/gm)].map((m) => [
m[1],
m[2],
]),
);

it("pins the role count at 10", () => {
Expand All @@ -107,7 +108,7 @@ describe("theme bridge", () => {
const expected = new Map<string, string>();
for (const role of defined.keys()) {
for (const slot of slots) {
expected.set(`--text-${role}${suffix[slot]}`, `--app-shell-type-${role}-${slot}`);
expected.set(`--text-${role}${suffix[slot]}`, `--type-${role}-${slot}`);
}
}
expect(bridgedText).toEqual(expected);
Expand Down
80 changes: 40 additions & 40 deletions packages/core/src/assets/theme.bridge.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,46 +45,46 @@
* The `--text-*` namespace cannot set font-family. `text-code-sm` is used with
* `font-mono`.
*/
--text-heading-lg: var(--app-shell-type-heading-lg-size);
--text-heading-lg--line-height: var(--app-shell-type-heading-lg-line-height);
--text-heading-lg--font-weight: var(--app-shell-type-heading-lg-weight);
--text-heading-lg--letter-spacing: var(--app-shell-type-heading-lg-letter-spacing);
--text-heading-md: var(--app-shell-type-heading-md-size);
--text-heading-md--line-height: var(--app-shell-type-heading-md-line-height);
--text-heading-md--font-weight: var(--app-shell-type-heading-md-weight);
--text-heading-md--letter-spacing: var(--app-shell-type-heading-md-letter-spacing);
--text-heading-sm: var(--app-shell-type-heading-sm-size);
--text-heading-sm--line-height: var(--app-shell-type-heading-sm-line-height);
--text-heading-sm--font-weight: var(--app-shell-type-heading-sm-weight);
--text-heading-sm--letter-spacing: var(--app-shell-type-heading-sm-letter-spacing);
--text-body-md: var(--app-shell-type-body-md-size);
--text-body-md--line-height: var(--app-shell-type-body-md-line-height);
--text-body-md--font-weight: var(--app-shell-type-body-md-weight);
--text-body-md--letter-spacing: var(--app-shell-type-body-md-letter-spacing);
--text-body-sm: var(--app-shell-type-body-sm-size);
--text-body-sm--line-height: var(--app-shell-type-body-sm-line-height);
--text-body-sm--font-weight: var(--app-shell-type-body-sm-weight);
--text-body-sm--letter-spacing: var(--app-shell-type-body-sm-letter-spacing);
--text-label-md: var(--app-shell-type-label-md-size);
--text-label-md--line-height: var(--app-shell-type-label-md-line-height);
--text-label-md--font-weight: var(--app-shell-type-label-md-weight);
--text-label-md--letter-spacing: var(--app-shell-type-label-md-letter-spacing);
--text-label-sm: var(--app-shell-type-label-sm-size);
--text-label-sm--line-height: var(--app-shell-type-label-sm-line-height);
--text-label-sm--font-weight: var(--app-shell-type-label-sm-weight);
--text-label-sm--letter-spacing: var(--app-shell-type-label-sm-letter-spacing);
--text-code-sm: var(--app-shell-type-code-sm-size);
--text-code-sm--line-height: var(--app-shell-type-code-sm-line-height);
--text-code-sm--font-weight: var(--app-shell-type-code-sm-weight);
--text-code-sm--letter-spacing: var(--app-shell-type-code-sm-letter-spacing);
--text-body-md-relaxed: var(--app-shell-type-body-md-relaxed-size);
--text-body-md-relaxed--line-height: var(--app-shell-type-body-md-relaxed-line-height);
--text-body-md-relaxed--font-weight: var(--app-shell-type-body-md-relaxed-weight);
--text-body-md-relaxed--letter-spacing: var(--app-shell-type-body-md-relaxed-letter-spacing);
--text-body-sm-relaxed: var(--app-shell-type-body-sm-relaxed-size);
--text-body-sm-relaxed--line-height: var(--app-shell-type-body-sm-relaxed-line-height);
--text-body-sm-relaxed--font-weight: var(--app-shell-type-body-sm-relaxed-weight);
--text-body-sm-relaxed--letter-spacing: var(--app-shell-type-body-sm-relaxed-letter-spacing);
--text-heading-lg: var(--type-heading-lg-size);
--text-heading-lg--line-height: var(--type-heading-lg-line-height);
--text-heading-lg--font-weight: var(--type-heading-lg-weight);
--text-heading-lg--letter-spacing: var(--type-heading-lg-letter-spacing);
--text-heading-md: var(--type-heading-md-size);
--text-heading-md--line-height: var(--type-heading-md-line-height);
--text-heading-md--font-weight: var(--type-heading-md-weight);
--text-heading-md--letter-spacing: var(--type-heading-md-letter-spacing);
--text-heading-sm: var(--type-heading-sm-size);
--text-heading-sm--line-height: var(--type-heading-sm-line-height);
--text-heading-sm--font-weight: var(--type-heading-sm-weight);
--text-heading-sm--letter-spacing: var(--type-heading-sm-letter-spacing);
--text-body-md: var(--type-body-md-size);
--text-body-md--line-height: var(--type-body-md-line-height);
--text-body-md--font-weight: var(--type-body-md-weight);
--text-body-md--letter-spacing: var(--type-body-md-letter-spacing);
--text-body-sm: var(--type-body-sm-size);
--text-body-sm--line-height: var(--type-body-sm-line-height);
--text-body-sm--font-weight: var(--type-body-sm-weight);
--text-body-sm--letter-spacing: var(--type-body-sm-letter-spacing);
--text-label-md: var(--type-label-md-size);
--text-label-md--line-height: var(--type-label-md-line-height);
--text-label-md--font-weight: var(--type-label-md-weight);
--text-label-md--letter-spacing: var(--type-label-md-letter-spacing);
--text-label-sm: var(--type-label-sm-size);
--text-label-sm--line-height: var(--type-label-sm-line-height);
--text-label-sm--font-weight: var(--type-label-sm-weight);
--text-label-sm--letter-spacing: var(--type-label-sm-letter-spacing);
--text-code-sm: var(--type-code-sm-size);
--text-code-sm--line-height: var(--type-code-sm-line-height);
--text-code-sm--font-weight: var(--type-code-sm-weight);
--text-code-sm--letter-spacing: var(--type-code-sm-letter-spacing);
--text-body-md-relaxed: var(--type-body-md-relaxed-size);
--text-body-md-relaxed--line-height: var(--type-body-md-relaxed-line-height);
--text-body-md-relaxed--font-weight: var(--type-body-md-relaxed-weight);
--text-body-md-relaxed--letter-spacing: var(--type-body-md-relaxed-letter-spacing);
--text-body-sm-relaxed: var(--type-body-sm-relaxed-size);
--text-body-sm-relaxed--line-height: var(--type-body-sm-relaxed-line-height);
--text-body-sm-relaxed--font-weight: var(--type-body-sm-relaxed-weight);
--text-body-sm-relaxed--letter-spacing: var(--type-body-sm-relaxed-letter-spacing);

--color-background: var(--background);
--color-foreground: var(--foreground);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/assets/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
* Inherited from default.css — do not duplicate in new palettes:
* --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*,
* --app-shell-type-* (typography roles).
* --type-* (typography roles).
* The colour roles --{intent}-{role} inherit too; a palette overrides them
* only to recolour the status components. Typography roles are inherited
* the same way: do not copy the defaults, override only the variables
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/assets/themes/_template.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
*
* DO NOT SET (inherited from `:root` in default.css):
* --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*,
* --app-shell-type-*
* --type-*
* The colour roles --{info|success|warning|danger|neutral}-{role} also inherit.
* Override a role only to recolour the status components (Badge, Alert, ...)
* for this palette; never set --status-* or --alert-*, they alias the roles.
* Typography roles (--app-shell-type-*) inherit too: do not copy the defaults,
* Typography roles (--type-*) inherit too: do not copy the defaults,
* override only the variables whose values differ.
*
* PREVIEW: `/showcase/colors` in the Vite example app.
Expand Down
82 changes: 41 additions & 41 deletions packages/core/src/assets/themes/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -435,48 +435,48 @@
*
* Four variables per role: size, line-height (unitless ratio), weight and
* letter-spacing. theme.bridge.css maps them to `text-<role>` utilities.
* Palettes inherit these; override `--app-shell-type-*` on `:root` to change
* Palettes inherit these; override `--type-*` on `:root` to change
* a role. See decisions/typography-roles.md.
*/
:root {
--app-shell-type-heading-lg-size: 1.5rem;
--app-shell-type-heading-lg-line-height: calc(32 / 24);
--app-shell-type-heading-lg-weight: 700;
--app-shell-type-heading-lg-letter-spacing: -0.025em;
--app-shell-type-heading-md-size: 1.125rem;
--app-shell-type-heading-md-line-height: calc(28 / 18);
--app-shell-type-heading-md-weight: 600;
--app-shell-type-heading-md-letter-spacing: 0em;
--app-shell-type-heading-sm-size: 0.875rem;
--app-shell-type-heading-sm-line-height: calc(20 / 14);
--app-shell-type-heading-sm-weight: 600;
--app-shell-type-heading-sm-letter-spacing: 0em;
--app-shell-type-body-md-size: 0.875rem;
--app-shell-type-body-md-line-height: calc(20 / 14);
--app-shell-type-body-md-weight: 400;
--app-shell-type-body-md-letter-spacing: 0em;
--app-shell-type-body-sm-size: 0.75rem;
--app-shell-type-body-sm-line-height: calc(16 / 12);
--app-shell-type-body-sm-weight: 400;
--app-shell-type-body-sm-letter-spacing: 0em;
--app-shell-type-label-md-size: 0.875rem;
--app-shell-type-label-md-line-height: calc(20 / 14);
--app-shell-type-label-md-weight: 500;
--app-shell-type-label-md-letter-spacing: 0em;
--app-shell-type-label-sm-size: 0.75rem;
--app-shell-type-label-sm-line-height: calc(16 / 12);
--app-shell-type-label-sm-weight: 500;
--app-shell-type-label-sm-letter-spacing: 0em;
--app-shell-type-code-sm-size: 0.75rem;
--app-shell-type-code-sm-line-height: calc(18 / 12);
--app-shell-type-code-sm-weight: 400;
--app-shell-type-code-sm-letter-spacing: 0em;
--app-shell-type-body-md-relaxed-size: 0.875rem;
--app-shell-type-body-md-relaxed-line-height: calc(24 / 14);
--app-shell-type-body-md-relaxed-weight: 400;
--app-shell-type-body-md-relaxed-letter-spacing: 0em;
--app-shell-type-body-sm-relaxed-size: 0.75rem;
--app-shell-type-body-sm-relaxed-line-height: calc(20 / 12);
--app-shell-type-body-sm-relaxed-weight: 400;
--app-shell-type-body-sm-relaxed-letter-spacing: 0em;
--type-heading-lg-size: 1.5rem;
--type-heading-lg-line-height: calc(32 / 24);
--type-heading-lg-weight: 700;
--type-heading-lg-letter-spacing: -0.025em;
--type-heading-md-size: 1.125rem;
--type-heading-md-line-height: calc(28 / 18);
--type-heading-md-weight: 600;
--type-heading-md-letter-spacing: 0em;
--type-heading-sm-size: 0.875rem;
--type-heading-sm-line-height: calc(20 / 14);
--type-heading-sm-weight: 600;
--type-heading-sm-letter-spacing: 0em;
--type-body-md-size: 0.875rem;
--type-body-md-line-height: calc(20 / 14);
--type-body-md-weight: 400;
--type-body-md-letter-spacing: 0em;
--type-body-sm-size: 0.75rem;
--type-body-sm-line-height: calc(16 / 12);
--type-body-sm-weight: 400;
--type-body-sm-letter-spacing: 0em;
--type-label-md-size: 0.875rem;
--type-label-md-line-height: calc(20 / 14);
--type-label-md-weight: 500;
--type-label-md-letter-spacing: 0em;
--type-label-sm-size: 0.75rem;
--type-label-sm-line-height: calc(16 / 12);
--type-label-sm-weight: 500;
--type-label-sm-letter-spacing: 0em;
--type-code-sm-size: 0.75rem;
--type-code-sm-line-height: calc(18 / 12);
--type-code-sm-weight: 400;
--type-code-sm-letter-spacing: 0em;
--type-body-md-relaxed-size: 0.875rem;
--type-body-md-relaxed-line-height: calc(24 / 14);
--type-body-md-relaxed-weight: 400;
--type-body-md-relaxed-letter-spacing: 0em;
--type-body-sm-relaxed-size: 0.75rem;
--type-body-sm-relaxed-line-height: calc(20 / 12);
--type-body-sm-relaxed-weight: 400;
--type-body-sm-relaxed-letter-spacing: 0em;
}
2 changes: 1 addition & 1 deletion packages/core/src/lib/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const defaultTheme = readFileSync(
"utf8",
);
const roles = [
...new Set([...defaultTheme.matchAll(/^\s*--app-shell-type-([a-z-]+)-size:/gm)].map((m) => m[1])),
...new Set([...defaultTheme.matchAll(/^\s*--type-([a-z-]+)-size:/gm)].map((m) => m[1])),
];

describe("cn", () => {
Expand Down
Loading
Loading