feat: enhance typography and accessibility in design system

- Added new UI heading utility with specific styles in typography.css.
- Updated typography tests to include new UI heading role and ensure proper size and styling.
- Documented design system audit findings, focusing on accessibility improvements and component consistency.
- Introduced CalendarExamples component to demonstrate calendar functionality with habit tracking.
- Created a shared Field component for consistent labeling and hinting of form controls.
- Implemented comprehensive accessibility tests for calendar and editor components, ensuring proper focus management and keyboard navigation.
- Added structure tests for Field and Card components to verify correct ID generation and heading levels.
This commit is contained in:
syntaxbullet
2026-09-04 13:44:31 +02:00
parent bfa54851c2
commit dde5e77317
19 changed files with 1077 additions and 681 deletions

View File

@@ -17,18 +17,19 @@
--ds-ink: #111;
--ds-secondary: #666;
--ds-rule: #dedede;
--ds-control-border: #767676;
--ds-paper: #fff;
--ds-surface: #f5f5f5;
--ds-empty: #eee;
--ds-serif: "Instrument Serif", Georgia, serif;
--ds-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
position: relative;
margin: -8px;
min-height: 100vh;
color: var(--ds-ink);
background: var(--ds-paper);
@apply type-body;
font-family: -apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
font-family: var(--ds-sans);
-webkit-font-smoothing: antialiased;
}
.ds-root *,
@@ -83,7 +84,7 @@
}
.ds-eyebrow {
@apply type-eyebrow;
color: #737373;
color: var(--ds-secondary);
}
.ds-header {
max-width: 1280px;
@@ -137,10 +138,10 @@
.ds-library-note { max-width: 180px; text-align: right; }
.ds-tab-bar {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 4px;
padding: 6px;
background: #f5f5f5;
background: var(--ds-surface);
}
.ds-tab-bar [role="tab"] {
@apply type-body;
@@ -149,7 +150,7 @@
border: 1px solid transparent;
padding: 10px 8px;
}
.ds-tab-bar [role="tab"]:hover { background: #ebebeb; color: var(--ds-ink); }
.ds-tab-bar [role="tab"]:hover { background: var(--ds-surface); color: var(--ds-ink); }
.ds-tab-bar [aria-selected="true"] {
background: var(--ds-paper);
color: var(--ds-ink);
@@ -326,7 +327,7 @@
color: var(--ds-paper);
}
.ds-button:hover:not(:disabled) {
background: #333;
background: var(--ds-secondary);
}
.ds-button-icon { width: 1.25rem; height: 1.25rem; flex-shrink: 0; }
.ds-button--secondary {
@@ -337,21 +338,21 @@
background: transparent;
color: var(--ds-ink);
border-color: transparent;
padding-left: 0;
padding-right: 0;
}
.ds-button--secondary:hover:not(:disabled),
.ds-button--text:hover:not(:disabled) {
background: #f3f3f3;
background: var(--ds-surface);
}
.ds-counter {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.ds-counter .ds-button {
width: 36px;
min-height: 40px;
width: 44px;
min-width: 44px;
min-height: 44px;
@apply type-control;
font-weight: 300;
padding: 0;
@@ -365,7 +366,7 @@
display: inline-flex;
align-items: center;
gap: 11px;
min-height: 36px;
min-height: 44px;
cursor: pointer;
@apply type-small;
}
@@ -375,8 +376,8 @@
width: 17px;
height: 17px;
flex: 0 0 17px;
border: 1px solid #777;
background: #fff;
border: 1px solid var(--ds-control-border);
background: var(--ds-paper);
display: grid;
place-content: center;
cursor: pointer;
@@ -389,8 +390,8 @@
content: "";
width: 8px;
height: 4px;
border-left: 1px solid #fff;
border-bottom: 1px solid #fff;
border-left: 1px solid var(--ds-paper);
border-bottom: 1px solid var(--ds-paper);
transform: rotate(-45deg) translateY(-1px);
}
.ds-checkbox input:disabled {
@@ -399,6 +400,8 @@
}
.ds-checkbox--icon {
padding: 10px;
min-width: 44px;
justify-content: center;
}
.ds-checkbox--icon span {
position: absolute;
@@ -449,12 +452,12 @@
box-shadow: none;
overflow-wrap: anywhere;
}
.ds-card--soft { background: #f5f5f5; }
.ds-card--soft { background: var(--ds-surface); }
.ds-card--outlined {
background: var(--ds-paper);
border-color: var(--ds-rule);
}
.ds-card-header h3 { @apply type-title; }
.ds-card-title { @apply type-title; }
.ds-card-header .ds-eyebrow { margin-bottom: 12px; }
.ds-card-body { display: grid; gap: 20px; }
.ds-card-body > p { color: var(--ds-secondary); }
@@ -505,7 +508,7 @@
}
.ds-spec-heading h3,
.ds-spec-heading label {
@apply type-small;
@apply type-ui-heading;
}
.ds-spec-heading--spaced {
margin-top: 36px;
@@ -533,7 +536,7 @@
}
.ds-palette {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 18px;
}
.ds-swatch {
@@ -550,9 +553,11 @@
display: block;
@apply type-small;
margin-top: 3px;
overflow-wrap: anywhere;
}
.ds-spacing {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 30px;
}
@@ -579,13 +584,14 @@
border-top: 1px solid var(--ds-rule);
padding: 24px 0;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 20px;
}
.ds-component-row h3 {
@apply type-small;
margin-bottom: 3px;
@apply type-ui-heading;
margin-bottom: 8px;
}
.ds-example-form {
border-top: 1px solid var(--ds-rule);
@@ -593,24 +599,19 @@
}
.ds-input-row {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
.ds-input-row input {
flex: 1;
flex: 1 1 12rem;
min-width: 0;
border: 0;
border-bottom: 1px solid #888;
padding: 10px 0;
background: var(--ds-paper);
color: var(--ds-ink);
outline-offset: 4px;
}
.ds-input-row input::placeholder {
color: var(--ds-secondary);
}
.ds-shade-scale {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-columns: repeat(9, minmax(0, 1fr));
gap: 14px;
}
.ds-shade-scale > div {
@@ -627,6 +628,8 @@
}
.ds-scale-caption {
display: flex;
flex-wrap: wrap;
gap: 8px 16px;
justify-content: space-between;
@apply type-small;
color: var(--ds-secondary);
@@ -645,12 +648,12 @@
gap: 13px;
}
.ds-neutral-states h4 {
@apply type-small;
@apply type-ui-heading;
}
.ds-neutral-states p {
@apply type-small;
color: var(--ds-secondary);
margin-top: 3px;
margin-top: 8px;
}
.ds-state-square {
flex: 0 0 22px;
@@ -658,7 +661,7 @@
height: 22px;
}
.ds-state-square--future {
background: #eeeeee;
background: var(--ds-empty);
}
.ds-reference-grid {
display: grid;
@@ -704,6 +707,8 @@
}
.ds-detail-count {
display: flex;
flex-wrap: wrap;
gap: 16px 24px;
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--ds-rule);
@@ -723,10 +728,28 @@
gap: 26px;
padding: 4px 0 22px;
}
.ds-calendar-scroll {
overflow-x: auto;
padding: 5px;
margin: -5px;
.ds-calendar {
min-width: 0;
container-type: inline-size;
--calendar-gap: 4px;
}
/* Shared axes and consecutive weeks; landscape fits without a scroll container. */
.ds-calendar-viewport {
padding: 4px;
}
.ds-calendar-timeline {
display: grid;
grid-template-columns: 36px minmax(0, 1fr);
gap: 12px 10px;
width: 100%;
max-width: calc(46px + var(--calendar-weeks) * 24px + (var(--calendar-weeks) - 1) * var(--calendar-gap));
}
.ds-calendar-months {
display: grid;
grid-column: 2;
grid-template-columns: repeat(var(--calendar-weeks), minmax(0, 1fr));
gap: var(--calendar-gap);
min-width: 0;
}
.ds-calendar-range-toolbar {
display: flex;
@@ -742,11 +765,29 @@
}
.ds-month-views {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.ds-calendar-custom-range {
display: flex;
flex-wrap: wrap;
align-items: end;
gap: 12px;
margin-bottom: 22px;
}
.ds-calendar-custom-range .ds-field {
flex: 1 1 160px;
margin-bottom: 0;
}
.ds-calendar-custom-range input { width: 100%; }
.ds-calendar-range-error {
flex-basis: 100%;
@apply type-small;
color: var(--ds-ink);
}
.ds-month-views button {
min-height: 32px;
min-width: 32px;
min-height: 44px;
min-width: 44px;
padding: 5px 7px;
color: var(--ds-secondary);
background: transparent;
@@ -756,32 +797,25 @@
}
.ds-month-views button:hover {
color: var(--ds-ink);
background: #f3f3f3;
background: var(--ds-surface);
}
.ds-month-views button[aria-pressed="true"] {
color: var(--ds-ink);
border-bottom-color: var(--ds-ink);
}
.ds-calendar-inner {
min-width: 600px;
}
.ds-calendar-months {
display: grid;
grid-template-columns: repeat(26, 1fr);
margin-left: 38px;
.ds-calendar-month-label {
min-width: 0;
@apply type-small;
color: var(--ds-secondary);
height: 28px;
}
.ds-calendar-body {
display: flex;
gap: 10px;
}
.ds-calendar-weekdays {
display: grid;
grid-template-rows: repeat(7, 1fr);
width: 36px;
flex-shrink: 0;
grid-column: 1;
grid-template-rows: repeat(7, minmax(0, 1fr));
min-height: 0;
height: 100%;
contain: size;
gap: var(--calendar-gap);
@apply type-small;
color: var(--ds-secondary);
align-items: center;
@@ -797,18 +831,29 @@
}
.ds-calendar-grid {
display: grid;
grid-column: 2;
grid-template-columns: repeat(var(--calendar-weeks), minmax(0, 1fr));
grid-template-rows: repeat(7, 1fr);
grid-auto-flow: column;
gap: 6px;
flex: 1;
min-width: 0;
gap: var(--calendar-gap);
}
.ds-day {
padding: 0;
aspect-ratio: 1;
border: 1px solid transparent;
width: 100%;
min-width: 0;
min-height: 0;
}
/* Keep a full-size date target, but reserve filled squares for tracked days. */
/* Only narrow portrait screens scroll, preserving comfortable date targets. */
@media (max-width: 600px) and (orientation: portrait) {
.ds-calendar-viewport { overflow-x: auto; }
.ds-day { min-width: 24px; min-height: 24px; }
.ds-calendar-timeline {
min-width: calc(46px + var(--calendar-weeks) * 24px + (var(--calendar-weeks) - 1) * var(--calendar-gap));
}
}
/* Reserve filled squares for tracked days. */
.ds-day--not-due,
.ds-legend-swatch--not-due,
.ds-state-square--not-due {
@@ -825,7 +870,7 @@
width: 3px;
height: 3px;
border-radius: 50%;
background: #888;
background: var(--ds-secondary);
transform: translate(-50%, -50%);
pointer-events: none;
}
@@ -890,7 +935,7 @@
top: 0;
left: 24px;
padding: 10px;
background: white;
background: var(--ds-paper);
transform: translateY(-110%);
z-index: 2;
}
@@ -957,7 +1002,7 @@
gap: 30px;
}
.ds-section-heading p br {
display: none;
display: initial;
}
.ds-view-switch {
gap: 18px;
@@ -980,7 +1025,7 @@
gap: 2px;
}
.ds-counter .ds-button {
width: 32px;
width: 44px;
}
.ds-task-count {
min-width: 106px;
@@ -992,6 +1037,7 @@
gap: 3px 20px;
}
.ds-palette {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 13px;
}
.ds-swatch {
@@ -1049,14 +1095,15 @@
padding-top: 20px;
}
.ds-habit-chart:nth-child(n + 3),
.ds-habit-chart:has(.ds-calendar[data-months="12"]) + .ds-habit-chart {
.ds-habit-chart:has(.ds-calendar:is([data-months="12"], [data-months="custom"])) + .ds-habit-chart {
border-top: 1px solid var(--ds-rule);
}
.ds-habit-chart:has(.ds-calendar[data-months="12"]) {
.ds-habit-chart:has(.ds-calendar:is([data-months="12"], [data-months="custom"])) {
grid-column: 1 / -1;
}
.ds-habit-chart-heading {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 10px;
@@ -1086,7 +1133,7 @@
gap: 4px;
}
.ds-habit-chart-heading .ds-counter .ds-button {
width: 28px;
width: 44px;
}
.ds-habit-chart-progress {
display: flex;
@@ -1099,12 +1146,6 @@
grid-column: 1 / -1;
margin-top: -12px !important;
}
.ds-calendar--compact .ds-calendar-inner {
min-width: 380px;
}
.ds-calendar--compact .ds-calendar-grid {
gap: 5px;
}
.ds-calendar--compact .ds-date-inspector {
flex-direction: column;
gap: 5px;
@@ -1168,6 +1209,10 @@
height: 10px;
}
@media (max-width: 760px) {
.ds-split-section {
grid-template-columns: minmax(0, 1fr);
gap: 30px;
}
.ds-habit-chart + .ds-habit-chart {
border-top: 1px solid var(--ds-rule);
}
@@ -1175,43 +1220,30 @@
grid-template-columns: minmax(0, 1fr);
gap: 32px;
}
.ds-calendar--compact .ds-calendar-inner {
max-width: 520px;
}
.ds-habit-chart-heading .ds-counter {
gap: 8px;
}
}
/* A full year gets a full row; fit every week instead of clipping or scrolling. */
.ds-calendar[data-months="12"] .ds-calendar-inner {
width: 100%;
max-width: none;
}
.ds-calendar[data-months="12"] .ds-calendar-grid {
min-width: 0;
gap: clamp(2px, 0.4vw, 6px);
}
.ds-legend-scale { flex-wrap: wrap; }
/* Editing patterns use the same open layout, rules, and neutral controls. */
.ds-color-picker { border: 0; border-top: 1px solid var(--ds-rule); padding: 20px 0 0; margin: 26px 0; min-width: 0; }
.ds-color-picker legend { padding: 0 12px 0 0; @apply type-small; }
.ds-color-picker legend { padding: 0 12px 0 0; @apply type-ui-heading; }
.ds-color-picker > .ds-footnote { margin-top: 0 !important; }
.ds-color-palettes { display: flex; flex-wrap: wrap; gap: 20px 28px; margin: 20px 0; }
.ds-color-palette > span { @apply type-small; color: var(--ds-secondary); }
.ds-color-swatches { display: flex; gap: 4px; margin-top: 8px; }
.ds-color-swatches button { width: 38px; min-height: 44px; padding: 4px; border: 1px solid transparent; background: transparent; }
.ds-color-swatches button { display: grid; place-items: center; width: 44px; min-width: 44px; min-height: 44px; padding: 4px; border: 1px solid transparent; background: transparent; }
.ds-color-swatches button[aria-pressed="true"] { border-color: var(--ds-ink); }
.ds-color-swatches button > span { display: grid; place-content: center; height: 28px; width: 28px; color: white; @apply type-body; }
.ds-custom-color { display: flex; gap: 16px; align-items: start; }
.ds-color-swatches button > span { display: grid; place-content: center; height: 28px; width: 28px; color: var(--ds-paper); @apply type-body; }
.ds-custom-color { display: flex; flex-wrap: wrap; gap: 16px; align-items: start; }
.ds-custom-color .ds-field { margin-bottom: 0; }
.ds-custom-color .ds-field:last-child { width: 180px; }
.ds-custom-color input[type="color"] { width: 80px; height: 44px; padding: 5px; cursor: pointer; }
.ds-color-live-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; margin-top: 22px; @apply type-small; }
.ds-color-preview-shades { display: flex; gap: 4px; }
.ds-color-preview-shades > span { width: 18px; height: 18px; border: 1px solid #dedede; }
.ds-color-preview-shades > span { width: 18px; height: 18px; border: 1px solid var(--ds-rule); }
.ds-edit-layout {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
@@ -1229,12 +1261,12 @@
.ds-edit-meta { display: flex; align-items: center; gap: 10px; margin-top: 24px; overflow-wrap: anywhere; }
.ds-habit-dot { width: 8px; height: 8px; flex: 0 0 8px; }
.ds-field { display: grid; min-width: 0; gap: 8px; margin-bottom: 22px; }
.ds-field label, .ds-weekday-field legend { @apply type-small; }
.ds-field label, .ds-weekday-field legend { @apply type-ui-heading; }
.ds-field :is(input, select) {
width: 100%;
min-width: 0;
min-height: 44px;
border: 1px solid #999;
border: 1px solid var(--ds-control-border);
padding: 10px 12px;
font: inherit;
@apply type-body;
@@ -1249,13 +1281,14 @@
.ds-edit-content .ds-checkbox { @apply type-small; }
.ds-weekday-field, .ds-task-editor-fieldset { border: 0; padding: 0; margin: 0 0 20px; min-width: 0; }
.ds-weekdays { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.ds-weekdays button { min-height: 44px; min-width: 44px; padding: 8px; border: 1px solid var(--ds-rule); background: white; color: var(--ds-secondary); }
.ds-weekdays button[aria-pressed="true"] { color: white; background: var(--ds-ink); border-color: var(--ds-ink); }
.ds-weekdays button { min-height: 44px; min-width: 44px; padding: 8px; border: 1px solid var(--ds-control-border); background: var(--ds-paper); color: var(--ds-secondary); }
.ds-weekdays button[aria-pressed="true"] { color: var(--ds-paper); background: var(--ds-ink); border-color: var(--ds-ink); }
.ds-form-actions { display: flex; flex-wrap: wrap; gap: 24px; align-items: center; }
.ds-form-feedback { min-height: 24px; margin: 20px 0 12px !important; color: var(--ds-secondary); @apply type-small; overflow-wrap: anywhere; }
.ds-form-feedback[role="alert"] { border-left: 2px solid var(--ds-ink); padding-left: 12px; color: var(--ds-ink); }
.ds-save-notice { margin-bottom: 0 !important; }
.ds-edit-task { border-top: 1px solid var(--ds-rule); padding: 16px 0 10px; }
.ds-edit-task { min-width: 0; margin: 0; border: 0; border-top: 1px solid var(--ds-rule); padding: 16px 0 10px; }
.ds-edit-task > legend { padding: 0 12px 0 0; @apply type-ui-heading; }
.ds-edit-task .ds-spec-heading { margin-bottom: 16px; }
.ds-empty-state { padding: 24px 0; color: var(--ds-secondary); }
.ds-history-requirement { display: flex; justify-content: space-between; gap: 16px; border-block: 1px solid var(--ds-rule); padding: 18px 0; margin-bottom: 24px; }
@@ -1284,4 +1317,20 @@
@media (max-width: 640px) {
.ds-header,
.ds-preview-toolbar { flex-wrap: wrap; }
.ds-view-switch { flex-wrap: wrap; gap: 0 18px; }
}
@media (forced-colors: active) {
.ds-checkbox input {
appearance: auto;
background: Canvas;
border-color: ButtonText;
}
.ds-checkbox input:checked::after { content: none; }
.ds-root :is(button, input):disabled { color: GrayText; opacity: 1; }
.ds-day { border-color: ButtonText; }
.ds-day[aria-pressed="true"] { outline-color: Highlight; }
.ds-tab-bar [aria-selected="true"],
.ds-month-views [aria-pressed="true"],
.ds-weekdays [aria-pressed="true"] { border-color: Highlight; }
}

View File

@@ -5,6 +5,8 @@
--text-copy--line-height: 1.5;
--text-caption: 0.875rem;
--text-caption--line-height: 1.5;
--text-ui-heading: 1.125rem;
--text-ui-heading--line-height: 1.5;
--text-lead: 1.25rem;
--text-lead--line-height: 1.5;
--text-title: 2rem;
@@ -17,6 +19,12 @@
@utility type-body { @apply text-copy; }
@utility type-small { @apply text-caption; }
@utility type-ui-heading {
@apply text-ui-heading;
font-family: var(--ds-sans);
font-weight: 500;
letter-spacing: normal;
}
@utility type-label {
@apply text-caption;
font-weight: 500;

View File

@@ -3,6 +3,16 @@ import { readFileSync } from "node:fs";
const css = (name: string) => readFileSync(new URL(name, import.meta.url), "utf8");
function tokenLuminance(source: string, token: string) {
let hex = source.match(new RegExp(`--ds-${token}: #([\\da-f]+);`))![1]!;
if (hex.length === 3) hex = [...hex].map((c) => c + c).join("");
const channels = [0, 2, 4].map((start) => {
const value = parseInt(hex.slice(start, start + 2), 16) / 255;
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
});
return channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722;
}
describe("shared typography contract", () => {
test("all component and page styles use the fixed shared type utilities", () => {
for (const file of ["design-system.css"]) {
@@ -15,24 +25,50 @@ describe("shared typography contract", () => {
expect(typography).not.toMatch(/clamp\(|\d(?:vw|vh|px)\b/);
expect(typography).toContain("--text-copy: 1rem");
expect(typography).toContain("--text-caption: 0.875rem");
for (const role of ["body", "small", "label", "lead", "title", "section", "display", "control"]) {
for (const role of ["body", "small", "label", "ui-heading", "eyebrow", "lead", "title", "section", "display", "control"]) {
expect(typography).toContain(`@utility type-${role}`);
}
});
test("text colors meet WCAG AA normal-text contrast on paper", () => {
const source = css("design-system.css");
const luminance = (token: string) => {
let hex = source.match(new RegExp(`--ds-${token}: #([\\da-f]+);`))![1]!;
if (hex.length === 3) hex = [...hex].map((c) => c + c).join("");
const channels = [0, 2, 4].map((start) => {
const value = parseInt(hex.slice(start, start + 2), 16) / 255;
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
});
return channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722;
};
for (const color of ["ink", "secondary"]) {
expect((luminance("paper") + 0.05) / (luminance(color) + 0.05)).toBeGreaterThanOrEqual(4.5);
expect((tokenLuminance(source, "paper") + 0.05) / (tokenLuminance(source, color) + 0.05)).toBeGreaterThanOrEqual(4.5);
}
});
test("UI headings are larger than supporting text and reset editorial typography", () => {
const rule = css("typography.css").match(/@utility type-ui-heading\s*\{([^}]+)\}/)![1]!;
expect(rule).toContain("@apply text-ui-heading");
const source = css("typography.css");
const size = (role: string) => Number(source.match(new RegExp(`--text-${role}: ([\\d.]+)rem`))![1]);
expect(size("ui-heading")).toBe(1.125);
expect(size("ui-heading")).toBeGreaterThan(size("caption"));
expect(rule).toContain("font-family: var(--ds-sans)");
expect(rule).toContain("font-weight: 500");
expect(rule).toContain("letter-spacing: normal");
expect(css("design-system.css")).toMatch(/\.ds-spec-heading label\s*\{\s*@apply type-ui-heading/);
});
test("control boundaries contrast with paper and use a shared token", () => {
const source = css("design-system.css");
const ratio = (tokenLuminance(source, "paper") + 0.05) / (tokenLuminance(source, "control-border") + 0.05);
expect(ratio).toBeGreaterThanOrEqual(3);
const fieldRule = source.match(/\.ds-field :is\(input, select\)\s*\{([^}]+)\}/)![1]!;
expect(fieldRule).toContain("border: 1px solid var(--ds-control-border)");
});
test("shared controls retain usable targets and native forced-colors checkboxes", () => {
const source = css("design-system.css");
const counterRule = source.match(/\.ds-counter \.ds-button\s*\{([^}]+)\}/)![1]!;
expect(counterRule).toContain("min-width: 44px");
expect(counterRule).toContain("min-height: 44px");
const dayRule = source.match(/\.ds-day\s*\{([^}]+)\}/)![1]!;
expect(dayRule).toContain("min-width: 0");
expect(dayRule).toContain("aspect-ratio: 1");
expect(source).toContain("--calendar-gap: 4px;");
expect(source).toContain("@media (max-width: 600px) and (orientation: portrait)");
expect(source).toContain(".ds-day { min-width: 24px; min-height: 24px; }");
expect(source).toMatch(/@media \(forced-colors: active\)\s*\{\s*\.ds-checkbox input\s*\{\s*appearance: auto/);
});
});