Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelog.d/contrast-matrix-coverage.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Contrast matrix now asserts the focus ring on background (light + dark, WCAG SC 1.4.11 non-text 3:1), dark-mode `primary-foreground` on `primary`, dark-mode `destructive-foreground` on `destructive`, and the default link color (teal-600) on background.
1 change: 1 addition & 0 deletions changelog.d/dark-card-visibility.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dark mode card surfaces are now visible. `--card`/`--popover`/`--muted` bumped from `#131820` (1.08:1 vs background — visually invisible) to `#1A2030` (1.19:1), and `--border`/`--input` bumped from `#1E293B` (1.32:1 vs background, 1.22:1 vs card) to `#334155` (1.87:1 / 1.57:1) so card surfaces and their borders are clearly distinguishable.
1 change: 1 addition & 0 deletions changelog.d/quarto-secondary-fill.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Quarto SCSS `$secondary` now resolves to the secondary surface fill (`#F2F4F7`) instead of the secondary foreground (`#101828`). Bootstrap's `$secondary` is a fill color (e.g. `.btn-secondary` background), so paper renders previously got a near-black secondary button where Bootstrap expects a light gray.
5 changes: 4 additions & 1 deletion scripts/generate-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,10 @@ function buildQuartoScss(): string {
"/*-- scss:defaults --*/",
"",
`$primary: ${rootColorsLight["--primary"]};`,
`$secondary: ${rootColorsLight["--secondary-foreground"]};`,
// Bootstrap's $secondary is a *fill* color (e.g. .btn-secondary background),
// not a foreground. Use --secondary, the light gray surface, so paper
// buttons match the dashboard's secondary surface, not its dark text.
`$secondary: ${rootColorsLight["--secondary"]};`,
`$success: ${semanticFills.success};`,
`$warning: ${semanticFills.warning};`,
`$danger: ${semanticFills.error};`,
Expand Down
2 changes: 1 addition & 1 deletion src/theme/quarto.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/*-- scss:defaults --*/

$primary: #2C7A7B;
$secondary: #101828;
$secondary: #F2F4F7;
$success: #22C55E;
$warning: #FEC601;
$danger: #EF4444;
Expand Down
14 changes: 7 additions & 7 deletions src/theme/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
--secondary-foreground: #F5F5F5;

/* Muted */
--muted: #131820;
--muted: #1A2030;
--muted-foreground: #9CA3AF;

/* Accent */
Expand All @@ -135,17 +135,17 @@
--destructive: #F87171;
--destructive-foreground: #0B0E14;

/* Chrome */
--border: #1E293B;
--input: #1E293B;
/* Chrome. --border bumped from #1E293B (1.32:1 on background, 1.22:1 on card — visually invisible) to #334155 (1.87:1 on bg, 1.57:1 on card). */
--border: #334155;
--input: #334155;
--ring: #38B2AC;

/* Card */
--card: #131820;
/* Card. Bumped from #131820 (1.08:1 on background — invisible) to #1A2030 (1.19:1 on bg). Together with the bumped --border, the card surface is now clearly distinguishable. */
--card: #1A2030;
--card-foreground: #F5F5F5;

/* Popover */
--popover: #131820;
--popover: #1A2030;
--popover-foreground: #F5F5F5;

/* Charts (lifted up the brand scale for dark backgrounds) */
Expand Down
69 changes: 62 additions & 7 deletions src/theme/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ const lightSections: CssSection[] = [
},
},
{
// --ring at teal-500 (#319795) clears WCAG SC 1.4.11 (3:1 non-text)
// against white at 3.51:1 — only ~0.5 above the floor. If you nudge the
// ring lighter (toward teal-400), re-verify against the contrast matrix.
name: "Chrome",
declarations: {
"--border": "#E2E8F0",
Expand Down Expand Up @@ -212,7 +215,7 @@ const darkSections: CssSection[] = [
{
name: "Muted",
declarations: {
"--muted": "#131820",
"--muted": "#1A2030",
"--muted-foreground": "#9CA3AF",
},
},
Expand All @@ -231,24 +234,30 @@ const darkSections: CssSection[] = [
},
},
{
name: "Chrome",
name:
"Chrome. --border bumped from #1E293B (1.32:1 on background, 1.22:1 " +
"on card — visually invisible) to #334155 (1.87:1 on bg, 1.57:1 on " +
"card).",
declarations: {
"--border": "#1E293B",
"--input": "#1E293B",
"--border": "#334155",
"--input": "#334155",
"--ring": "#38B2AC",
},
},
{
name: "Card",
name:
"Card. Bumped from #131820 (1.08:1 on background — invisible) to " +
"#1A2030 (1.19:1 on bg). Together with the bumped --border, the card " +
"surface is now clearly distinguishable.",
declarations: {
"--card": "#131820",
"--card": "#1A2030",
"--card-foreground": "#F5F5F5",
},
},
{
name: "Popover",
declarations: {
"--popover": "#131820",
"--popover": "#1A2030",
"--popover-foreground": "#F5F5F5",
},
},
Expand Down Expand Up @@ -672,6 +681,25 @@ export const contrastPairs: readonly ContrastPair[] = [
minRatio: 4.5,
mode: "light",
},
{
// SC 1.4.11 (Non-text Contrast, AA): focus indicators must clear 3:1
// against the adjacent background. The ring sits on --background, so we
// pin its visibility there.
description: "ring on background (light, non-text)",
fg: rootColorsLight["--ring"],
bg: rootColorsLight["--background"],
minRatio: 3,
mode: "light",
},
{
// Default link styling resolves to teal-600. Asserted as small-text AA so
// links stay legible inside paragraph copy.
description: "link (teal-600) on background (light)",
fg: palette.teal[600],
bg: rootColorsLight["--background"],
minRatio: 4.5,
mode: "light",
},
// ----- Dark mode -----
{
description: "foreground on background (dark)",
Expand Down Expand Up @@ -729,4 +757,31 @@ export const contrastPairs: readonly ContrastPair[] = [
minRatio: 4.5,
mode: "dark",
},
{
// Dark-mode primary fill (teal-400) is light enough that we can ink it
// with near-black text. Pin the AA guarantee.
description: "primary-foreground on primary (dark)",
fg: rootColorsDark["--primary-foreground"],
bg: rootColorsDark["--primary"],
minRatio: 4.5,
mode: "dark",
},
{
// Same pattern for destructive: dark-mode destructive is red-400, so we
// ink with near-black to clear AA.
description: "destructive-foreground on destructive (dark)",
fg: rootColorsDark["--destructive-foreground"],
bg: rootColorsDark["--destructive"],
minRatio: 4.5,
mode: "dark",
},
{
// Focus indicator must clear SC 1.4.11 (3:1 non-text) against the
// adjacent background in dark mode too.
description: "ring on background (dark, non-text)",
fg: rootColorsDark["--ring"],
bg: rootColorsDark["--background"],
minRatio: 3,
mode: "dark",
},
];