@classic-homes/theme-tokens 0.1.51 → 0.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Color Contrast Utilities
3
+ *
4
+ * Utilities for calculating and validating color contrast ratios
5
+ * for WCAG 2.1 compliance.
6
+ *
7
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
8
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced.html
9
+ */
10
+ /**
11
+ * Calculates the relative luminance of a color.
12
+ *
13
+ * @param hex - Hex color string (e.g., '#ffffff' or '#fff')
14
+ * @returns Relative luminance value between 0 (black) and 1 (white)
15
+ *
16
+ * @see https://www.w3.org/WAI/GL/wiki/Relative_luminance
17
+ */
18
+ declare function getLuminance(hex: string): number;
19
+ /**
20
+ * Calculates the contrast ratio between two colors.
21
+ *
22
+ * @param foreground - Foreground/text color as hex
23
+ * @param background - Background color as hex
24
+ * @returns Contrast ratio (1:1 to 21:1)
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * getContrastRatio('#000000', '#ffffff'); // 21
29
+ * getContrastRatio('#ffffff', '#ffffff'); // 1
30
+ * ```
31
+ */
32
+ declare function getContrastRatio(foreground: string, background: string): number;
33
+ /**
34
+ * WCAG conformance levels for contrast
35
+ */
36
+ type WCAGLevel = 'AA' | 'AAA';
37
+ /**
38
+ * Text size categories for contrast requirements
39
+ */
40
+ type TextSize = 'normal' | 'large';
41
+ /**
42
+ * Checks if a color combination meets WCAG contrast requirements.
43
+ *
44
+ * WCAG 2.1 contrast requirements:
45
+ * - Level AA: 4.5:1 for normal text, 3:1 for large text
46
+ * - Level AAA: 7:1 for normal text, 4.5:1 for large text
47
+ *
48
+ * Large text is defined as 18pt (24px) or 14pt (18.66px) bold and larger.
49
+ *
50
+ * @param foreground - Foreground/text color as hex
51
+ * @param background - Background color as hex
52
+ * @param level - WCAG conformance level ('AA' or 'AAA')
53
+ * @param size - Text size category ('normal' or 'large')
54
+ * @returns True if the contrast meets the requirement
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * meetsWCAGContrast('#000000', '#ffffff', 'AA', 'normal'); // true (21:1 >= 4.5:1)
59
+ * meetsWCAGContrast('#666666', '#ffffff', 'AAA', 'normal'); // false (5.74:1 < 7:1)
60
+ * ```
61
+ */
62
+ declare function meetsWCAGContrast(foreground: string, background: string, level?: WCAGLevel, size?: TextSize): boolean;
63
+ /**
64
+ * Result of contrast validation with details
65
+ */
66
+ interface ContrastValidation {
67
+ /** Contrast ratio */
68
+ ratio: number;
69
+ /** Formatted ratio string (e.g., "4.5:1") */
70
+ ratioString: string;
71
+ /** Passes AA level for normal text */
72
+ passesAANormal: boolean;
73
+ /** Passes AA level for large text */
74
+ passesAALarge: boolean;
75
+ /** Passes AAA level for normal text */
76
+ passesAAANormal: boolean;
77
+ /** Passes AAA level for large text */
78
+ passesAAALarge: boolean;
79
+ }
80
+ /**
81
+ * Validates a color combination and returns detailed contrast information.
82
+ *
83
+ * @param foreground - Foreground/text color as hex
84
+ * @param background - Background color as hex
85
+ * @returns Detailed contrast validation results
86
+ */
87
+ declare function validateContrast(foreground: string, background: string): ContrastValidation;
88
+ /**
89
+ * Gets an accessible text color (black or white) for a given background.
90
+ *
91
+ * Uses luminance threshold to determine which color provides better contrast.
92
+ *
93
+ * @param background - Background color as hex
94
+ * @returns '#000000' for light backgrounds, '#ffffff' for dark backgrounds
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * getContrastTextColor('#ffffff'); // '#000000'
99
+ * getContrastTextColor('#000000'); // '#ffffff'
100
+ * getContrastTextColor('#3ba4a7'); // '#ffffff' (teal background)
101
+ * ```
102
+ */
103
+ declare function getContrastTextColor(background: string): '#000000' | '#ffffff';
104
+ /**
105
+ * Suggests the minimum lightness adjustment needed to meet contrast requirements.
106
+ *
107
+ * @param foreground - Current foreground color as hex
108
+ * @param background - Background color as hex
109
+ * @param level - Target WCAG level
110
+ * @param size - Text size category
111
+ * @returns Suggested adjustment or null if already passing
112
+ */
113
+ declare function suggestContrastAdjustment(foreground: string, background: string, level?: WCAGLevel, size?: TextSize): {
114
+ adjustLighter: boolean;
115
+ minimumRatio: number;
116
+ } | null;
117
+
118
+ export { type ContrastValidation, type TextSize, type WCAGLevel, getContrastRatio, getContrastTextColor, getLuminance, meetsWCAGContrast, suggestContrastAdjustment, validateContrast };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Color Contrast Utilities
3
+ *
4
+ * Utilities for calculating and validating color contrast ratios
5
+ * for WCAG 2.1 compliance.
6
+ *
7
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
8
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced.html
9
+ */
10
+ /**
11
+ * Calculates the relative luminance of a color.
12
+ *
13
+ * @param hex - Hex color string (e.g., '#ffffff' or '#fff')
14
+ * @returns Relative luminance value between 0 (black) and 1 (white)
15
+ *
16
+ * @see https://www.w3.org/WAI/GL/wiki/Relative_luminance
17
+ */
18
+ declare function getLuminance(hex: string): number;
19
+ /**
20
+ * Calculates the contrast ratio between two colors.
21
+ *
22
+ * @param foreground - Foreground/text color as hex
23
+ * @param background - Background color as hex
24
+ * @returns Contrast ratio (1:1 to 21:1)
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * getContrastRatio('#000000', '#ffffff'); // 21
29
+ * getContrastRatio('#ffffff', '#ffffff'); // 1
30
+ * ```
31
+ */
32
+ declare function getContrastRatio(foreground: string, background: string): number;
33
+ /**
34
+ * WCAG conformance levels for contrast
35
+ */
36
+ type WCAGLevel = 'AA' | 'AAA';
37
+ /**
38
+ * Text size categories for contrast requirements
39
+ */
40
+ type TextSize = 'normal' | 'large';
41
+ /**
42
+ * Checks if a color combination meets WCAG contrast requirements.
43
+ *
44
+ * WCAG 2.1 contrast requirements:
45
+ * - Level AA: 4.5:1 for normal text, 3:1 for large text
46
+ * - Level AAA: 7:1 for normal text, 4.5:1 for large text
47
+ *
48
+ * Large text is defined as 18pt (24px) or 14pt (18.66px) bold and larger.
49
+ *
50
+ * @param foreground - Foreground/text color as hex
51
+ * @param background - Background color as hex
52
+ * @param level - WCAG conformance level ('AA' or 'AAA')
53
+ * @param size - Text size category ('normal' or 'large')
54
+ * @returns True if the contrast meets the requirement
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * meetsWCAGContrast('#000000', '#ffffff', 'AA', 'normal'); // true (21:1 >= 4.5:1)
59
+ * meetsWCAGContrast('#666666', '#ffffff', 'AAA', 'normal'); // false (5.74:1 < 7:1)
60
+ * ```
61
+ */
62
+ declare function meetsWCAGContrast(foreground: string, background: string, level?: WCAGLevel, size?: TextSize): boolean;
63
+ /**
64
+ * Result of contrast validation with details
65
+ */
66
+ interface ContrastValidation {
67
+ /** Contrast ratio */
68
+ ratio: number;
69
+ /** Formatted ratio string (e.g., "4.5:1") */
70
+ ratioString: string;
71
+ /** Passes AA level for normal text */
72
+ passesAANormal: boolean;
73
+ /** Passes AA level for large text */
74
+ passesAALarge: boolean;
75
+ /** Passes AAA level for normal text */
76
+ passesAAANormal: boolean;
77
+ /** Passes AAA level for large text */
78
+ passesAAALarge: boolean;
79
+ }
80
+ /**
81
+ * Validates a color combination and returns detailed contrast information.
82
+ *
83
+ * @param foreground - Foreground/text color as hex
84
+ * @param background - Background color as hex
85
+ * @returns Detailed contrast validation results
86
+ */
87
+ declare function validateContrast(foreground: string, background: string): ContrastValidation;
88
+ /**
89
+ * Gets an accessible text color (black or white) for a given background.
90
+ *
91
+ * Uses luminance threshold to determine which color provides better contrast.
92
+ *
93
+ * @param background - Background color as hex
94
+ * @returns '#000000' for light backgrounds, '#ffffff' for dark backgrounds
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * getContrastTextColor('#ffffff'); // '#000000'
99
+ * getContrastTextColor('#000000'); // '#ffffff'
100
+ * getContrastTextColor('#3ba4a7'); // '#ffffff' (teal background)
101
+ * ```
102
+ */
103
+ declare function getContrastTextColor(background: string): '#000000' | '#ffffff';
104
+ /**
105
+ * Suggests the minimum lightness adjustment needed to meet contrast requirements.
106
+ *
107
+ * @param foreground - Current foreground color as hex
108
+ * @param background - Background color as hex
109
+ * @param level - Target WCAG level
110
+ * @param size - Text size category
111
+ * @returns Suggested adjustment or null if already passing
112
+ */
113
+ declare function suggestContrastAdjustment(foreground: string, background: string, level?: WCAGLevel, size?: TextSize): {
114
+ adjustLighter: boolean;
115
+ minimumRatio: number;
116
+ } | null;
117
+
118
+ export { type ContrastValidation, type TextSize, type WCAGLevel, getContrastRatio, getContrastTextColor, getLuminance, meetsWCAGContrast, suggestContrastAdjustment, validateContrast };
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/contrast.ts
21
+ var contrast_exports = {};
22
+ __export(contrast_exports, {
23
+ getContrastRatio: () => getContrastRatio,
24
+ getContrastTextColor: () => getContrastTextColor,
25
+ getLuminance: () => getLuminance,
26
+ meetsWCAGContrast: () => meetsWCAGContrast,
27
+ suggestContrastAdjustment: () => suggestContrastAdjustment,
28
+ validateContrast: () => validateContrast
29
+ });
30
+ module.exports = __toCommonJS(contrast_exports);
31
+ function hexToRgb(hex) {
32
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
33
+ if (result) {
34
+ return {
35
+ r: parseInt(result[1], 16),
36
+ g: parseInt(result[2], 16),
37
+ b: parseInt(result[3], 16)
38
+ };
39
+ }
40
+ const shortResult = /^#?([a-f\d])([a-f\d])([a-f\d])$/i.exec(hex);
41
+ if (shortResult) {
42
+ return {
43
+ r: parseInt(shortResult[1] + shortResult[1], 16),
44
+ g: parseInt(shortResult[2] + shortResult[2], 16),
45
+ b: parseInt(shortResult[3] + shortResult[3], 16)
46
+ };
47
+ }
48
+ return null;
49
+ }
50
+ function getLuminance(hex) {
51
+ const rgb = hexToRgb(hex);
52
+ if (!rgb) {
53
+ throw new Error(`Invalid hex color: ${hex}`);
54
+ }
55
+ const { r, g, b } = rgb;
56
+ const rsrgb = r / 255;
57
+ const gsrgb = g / 255;
58
+ const bsrgb = b / 255;
59
+ const rLinear = rsrgb <= 0.03928 ? rsrgb / 12.92 : Math.pow((rsrgb + 0.055) / 1.055, 2.4);
60
+ const gLinear = gsrgb <= 0.03928 ? gsrgb / 12.92 : Math.pow((gsrgb + 0.055) / 1.055, 2.4);
61
+ const bLinear = bsrgb <= 0.03928 ? bsrgb / 12.92 : Math.pow((bsrgb + 0.055) / 1.055, 2.4);
62
+ return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
63
+ }
64
+ function getContrastRatio(foreground, background) {
65
+ const l1 = getLuminance(foreground);
66
+ const l2 = getLuminance(background);
67
+ const lighter = Math.max(l1, l2);
68
+ const darker = Math.min(l1, l2);
69
+ return (lighter + 0.05) / (darker + 0.05);
70
+ }
71
+ function meetsWCAGContrast(foreground, background, level = "AA", size = "normal") {
72
+ const ratio = getContrastRatio(foreground, background);
73
+ const requirements = {
74
+ AA: { normal: 4.5, large: 3 },
75
+ AAA: { normal: 7, large: 4.5 }
76
+ };
77
+ return ratio >= requirements[level][size];
78
+ }
79
+ function validateContrast(foreground, background) {
80
+ const ratio = getContrastRatio(foreground, background);
81
+ return {
82
+ ratio,
83
+ ratioString: `${ratio.toFixed(2)}:1`,
84
+ passesAANormal: ratio >= 4.5,
85
+ passesAALarge: ratio >= 3,
86
+ passesAAANormal: ratio >= 7,
87
+ passesAAALarge: ratio >= 4.5
88
+ };
89
+ }
90
+ function getContrastTextColor(background) {
91
+ const luminance = getLuminance(background);
92
+ return luminance > 0.179 ? "#000000" : "#ffffff";
93
+ }
94
+ function suggestContrastAdjustment(foreground, background, level = "AA", size = "normal") {
95
+ if (meetsWCAGContrast(foreground, background, level, size)) {
96
+ return null;
97
+ }
98
+ const requirements = {
99
+ AA: { normal: 4.5, large: 3 },
100
+ AAA: { normal: 7, large: 4.5 }
101
+ };
102
+ const fgLuminance = getLuminance(foreground);
103
+ const bgLuminance = getLuminance(background);
104
+ return {
105
+ adjustLighter: fgLuminance < bgLuminance,
106
+ minimumRatio: requirements[level][size]
107
+ };
108
+ }
109
+ // Annotate the CommonJS export names for ESM import in node:
110
+ 0 && (module.exports = {
111
+ getContrastRatio,
112
+ getContrastTextColor,
113
+ getLuminance,
114
+ meetsWCAGContrast,
115
+ suggestContrastAdjustment,
116
+ validateContrast
117
+ });
@@ -0,0 +1,87 @@
1
+ // src/contrast.ts
2
+ function hexToRgb(hex) {
3
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
4
+ if (result) {
5
+ return {
6
+ r: parseInt(result[1], 16),
7
+ g: parseInt(result[2], 16),
8
+ b: parseInt(result[3], 16)
9
+ };
10
+ }
11
+ const shortResult = /^#?([a-f\d])([a-f\d])([a-f\d])$/i.exec(hex);
12
+ if (shortResult) {
13
+ return {
14
+ r: parseInt(shortResult[1] + shortResult[1], 16),
15
+ g: parseInt(shortResult[2] + shortResult[2], 16),
16
+ b: parseInt(shortResult[3] + shortResult[3], 16)
17
+ };
18
+ }
19
+ return null;
20
+ }
21
+ function getLuminance(hex) {
22
+ const rgb = hexToRgb(hex);
23
+ if (!rgb) {
24
+ throw new Error(`Invalid hex color: ${hex}`);
25
+ }
26
+ const { r, g, b } = rgb;
27
+ const rsrgb = r / 255;
28
+ const gsrgb = g / 255;
29
+ const bsrgb = b / 255;
30
+ const rLinear = rsrgb <= 0.03928 ? rsrgb / 12.92 : Math.pow((rsrgb + 0.055) / 1.055, 2.4);
31
+ const gLinear = gsrgb <= 0.03928 ? gsrgb / 12.92 : Math.pow((gsrgb + 0.055) / 1.055, 2.4);
32
+ const bLinear = bsrgb <= 0.03928 ? bsrgb / 12.92 : Math.pow((bsrgb + 0.055) / 1.055, 2.4);
33
+ return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
34
+ }
35
+ function getContrastRatio(foreground, background) {
36
+ const l1 = getLuminance(foreground);
37
+ const l2 = getLuminance(background);
38
+ const lighter = Math.max(l1, l2);
39
+ const darker = Math.min(l1, l2);
40
+ return (lighter + 0.05) / (darker + 0.05);
41
+ }
42
+ function meetsWCAGContrast(foreground, background, level = "AA", size = "normal") {
43
+ const ratio = getContrastRatio(foreground, background);
44
+ const requirements = {
45
+ AA: { normal: 4.5, large: 3 },
46
+ AAA: { normal: 7, large: 4.5 }
47
+ };
48
+ return ratio >= requirements[level][size];
49
+ }
50
+ function validateContrast(foreground, background) {
51
+ const ratio = getContrastRatio(foreground, background);
52
+ return {
53
+ ratio,
54
+ ratioString: `${ratio.toFixed(2)}:1`,
55
+ passesAANormal: ratio >= 4.5,
56
+ passesAALarge: ratio >= 3,
57
+ passesAAANormal: ratio >= 7,
58
+ passesAAALarge: ratio >= 4.5
59
+ };
60
+ }
61
+ function getContrastTextColor(background) {
62
+ const luminance = getLuminance(background);
63
+ return luminance > 0.179 ? "#000000" : "#ffffff";
64
+ }
65
+ function suggestContrastAdjustment(foreground, background, level = "AA", size = "normal") {
66
+ if (meetsWCAGContrast(foreground, background, level, size)) {
67
+ return null;
68
+ }
69
+ const requirements = {
70
+ AA: { normal: 4.5, large: 3 },
71
+ AAA: { normal: 7, large: 4.5 }
72
+ };
73
+ const fgLuminance = getLuminance(foreground);
74
+ const bgLuminance = getLuminance(background);
75
+ return {
76
+ adjustLighter: fgLuminance < bgLuminance,
77
+ minimumRatio: requirements[level][size]
78
+ };
79
+ }
80
+ export {
81
+ getContrastRatio,
82
+ getContrastTextColor,
83
+ getLuminance,
84
+ meetsWCAGContrast,
85
+ suggestContrastAdjustment,
86
+ validateContrast
87
+ };
package/dist/index.d.mts CHANGED
@@ -218,7 +218,7 @@ declare const brandColors: {
218
218
  */
219
219
  declare const statusColors: {
220
220
  readonly success: "#10b981";
221
- readonly warning: "#f59e0b";
221
+ readonly warning: "#d97706";
222
222
  readonly error: "#ef4444";
223
223
  readonly info: "#3b82f6";
224
224
  };
@@ -263,7 +263,7 @@ declare const semanticColors: {
263
263
  readonly ring: "#a91c22";
264
264
  readonly success: "#10b981";
265
265
  readonly successForeground: "#ffffff";
266
- readonly warning: "#f59e0b";
266
+ readonly warning: "#d97706";
267
267
  readonly warningForeground: "#ffffff";
268
268
  readonly info: "#3b82f6";
269
269
  readonly infoForeground: "#ffffff";
@@ -496,6 +496,60 @@ declare const transition: {
496
496
  readonly linear: "linear";
497
497
  };
498
498
  };
499
+ /**
500
+ * Focus Ring Tokens
501
+ *
502
+ * Consistent focus indicator styling for accessibility.
503
+ * These values ensure visible focus states that meet WCAG 2.1 requirements.
504
+ *
505
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html
506
+ */
507
+ declare const focusRing: {
508
+ /** Width of the focus ring outline */
509
+ readonly width: "2px";
510
+ /** Offset between the element and the focus ring */
511
+ readonly offset: "2px";
512
+ /** Outline style */
513
+ readonly style: "solid";
514
+ };
515
+ /**
516
+ * Animation Duration Tokens
517
+ *
518
+ * Includes reduced motion variants that should be used when
519
+ * the user has enabled `prefers-reduced-motion: reduce`.
520
+ *
521
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions.html
522
+ *
523
+ * @example CSS usage with media query
524
+ * ```css
525
+ * .animated-element {
526
+ * transition-duration: var(--animation-duration-base);
527
+ * }
528
+ *
529
+ * @media (prefers-reduced-motion: reduce) {
530
+ * .animated-element {
531
+ * transition-duration: var(--animation-duration-reduced-base);
532
+ * }
533
+ * }
534
+ * ```
535
+ */
536
+ declare const animation: {
537
+ /** Standard animation durations */
538
+ readonly duration: {
539
+ /** Fast animations like hover states */
540
+ readonly fast: "150ms";
541
+ /** Base duration for most UI animations */
542
+ readonly base: "200ms";
543
+ /** Slower animations for larger movements */
544
+ readonly slow: "300ms";
545
+ };
546
+ /** Reduced motion durations (instant or near-instant) */
547
+ readonly reducedMotion: {
548
+ readonly fast: "0ms";
549
+ readonly base: "0ms";
550
+ readonly slow: "0ms";
551
+ };
552
+ };
499
553
  declare const tokens: {
500
554
  readonly colors: {
501
555
  readonly white: "#ffffff";
@@ -702,7 +756,7 @@ declare const tokens: {
702
756
  };
703
757
  readonly statusColors: {
704
758
  readonly success: "#10b981";
705
- readonly warning: "#f59e0b";
759
+ readonly warning: "#d97706";
706
760
  readonly error: "#ef4444";
707
761
  readonly info: "#3b82f6";
708
762
  };
@@ -739,7 +793,7 @@ declare const tokens: {
739
793
  readonly ring: "#a91c22";
740
794
  readonly success: "#10b981";
741
795
  readonly successForeground: "#ffffff";
742
- readonly warning: "#f59e0b";
796
+ readonly warning: "#d97706";
743
797
  readonly warningForeground: "#ffffff";
744
798
  readonly info: "#3b82f6";
745
799
  readonly infoForeground: "#ffffff";
@@ -964,6 +1018,31 @@ declare const tokens: {
964
1018
  readonly linear: "linear";
965
1019
  };
966
1020
  };
1021
+ readonly focusRing: {
1022
+ /** Width of the focus ring outline */
1023
+ readonly width: "2px";
1024
+ /** Offset between the element and the focus ring */
1025
+ readonly offset: "2px";
1026
+ /** Outline style */
1027
+ readonly style: "solid";
1028
+ };
1029
+ readonly animation: {
1030
+ /** Standard animation durations */
1031
+ readonly duration: {
1032
+ /** Fast animations like hover states */
1033
+ readonly fast: "150ms";
1034
+ /** Base duration for most UI animations */
1035
+ readonly base: "200ms";
1036
+ /** Slower animations for larger movements */
1037
+ readonly slow: "300ms";
1038
+ };
1039
+ /** Reduced motion durations (instant or near-instant) */
1040
+ readonly reducedMotion: {
1041
+ readonly fast: "0ms";
1042
+ readonly base: "0ms";
1043
+ readonly slow: "0ms";
1044
+ };
1045
+ };
967
1046
  };
968
1047
 
969
- export { borderRadius, boxShadow, brandColors, breakpoints, colors, tokens as default, fontFamily, fontSize, fontWeight, layoutColors, semanticColors, spacing, statusColors, tokens, transition, zIndex };
1048
+ export { animation, borderRadius, boxShadow, brandColors, breakpoints, colors, tokens as default, focusRing, fontFamily, fontSize, fontWeight, layoutColors, semanticColors, spacing, statusColors, tokens, transition, zIndex };
package/dist/index.d.ts CHANGED
@@ -218,7 +218,7 @@ declare const brandColors: {
218
218
  */
219
219
  declare const statusColors: {
220
220
  readonly success: "#10b981";
221
- readonly warning: "#f59e0b";
221
+ readonly warning: "#d97706";
222
222
  readonly error: "#ef4444";
223
223
  readonly info: "#3b82f6";
224
224
  };
@@ -263,7 +263,7 @@ declare const semanticColors: {
263
263
  readonly ring: "#a91c22";
264
264
  readonly success: "#10b981";
265
265
  readonly successForeground: "#ffffff";
266
- readonly warning: "#f59e0b";
266
+ readonly warning: "#d97706";
267
267
  readonly warningForeground: "#ffffff";
268
268
  readonly info: "#3b82f6";
269
269
  readonly infoForeground: "#ffffff";
@@ -496,6 +496,60 @@ declare const transition: {
496
496
  readonly linear: "linear";
497
497
  };
498
498
  };
499
+ /**
500
+ * Focus Ring Tokens
501
+ *
502
+ * Consistent focus indicator styling for accessibility.
503
+ * These values ensure visible focus states that meet WCAG 2.1 requirements.
504
+ *
505
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html
506
+ */
507
+ declare const focusRing: {
508
+ /** Width of the focus ring outline */
509
+ readonly width: "2px";
510
+ /** Offset between the element and the focus ring */
511
+ readonly offset: "2px";
512
+ /** Outline style */
513
+ readonly style: "solid";
514
+ };
515
+ /**
516
+ * Animation Duration Tokens
517
+ *
518
+ * Includes reduced motion variants that should be used when
519
+ * the user has enabled `prefers-reduced-motion: reduce`.
520
+ *
521
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions.html
522
+ *
523
+ * @example CSS usage with media query
524
+ * ```css
525
+ * .animated-element {
526
+ * transition-duration: var(--animation-duration-base);
527
+ * }
528
+ *
529
+ * @media (prefers-reduced-motion: reduce) {
530
+ * .animated-element {
531
+ * transition-duration: var(--animation-duration-reduced-base);
532
+ * }
533
+ * }
534
+ * ```
535
+ */
536
+ declare const animation: {
537
+ /** Standard animation durations */
538
+ readonly duration: {
539
+ /** Fast animations like hover states */
540
+ readonly fast: "150ms";
541
+ /** Base duration for most UI animations */
542
+ readonly base: "200ms";
543
+ /** Slower animations for larger movements */
544
+ readonly slow: "300ms";
545
+ };
546
+ /** Reduced motion durations (instant or near-instant) */
547
+ readonly reducedMotion: {
548
+ readonly fast: "0ms";
549
+ readonly base: "0ms";
550
+ readonly slow: "0ms";
551
+ };
552
+ };
499
553
  declare const tokens: {
500
554
  readonly colors: {
501
555
  readonly white: "#ffffff";
@@ -702,7 +756,7 @@ declare const tokens: {
702
756
  };
703
757
  readonly statusColors: {
704
758
  readonly success: "#10b981";
705
- readonly warning: "#f59e0b";
759
+ readonly warning: "#d97706";
706
760
  readonly error: "#ef4444";
707
761
  readonly info: "#3b82f6";
708
762
  };
@@ -739,7 +793,7 @@ declare const tokens: {
739
793
  readonly ring: "#a91c22";
740
794
  readonly success: "#10b981";
741
795
  readonly successForeground: "#ffffff";
742
- readonly warning: "#f59e0b";
796
+ readonly warning: "#d97706";
743
797
  readonly warningForeground: "#ffffff";
744
798
  readonly info: "#3b82f6";
745
799
  readonly infoForeground: "#ffffff";
@@ -964,6 +1018,31 @@ declare const tokens: {
964
1018
  readonly linear: "linear";
965
1019
  };
966
1020
  };
1021
+ readonly focusRing: {
1022
+ /** Width of the focus ring outline */
1023
+ readonly width: "2px";
1024
+ /** Offset between the element and the focus ring */
1025
+ readonly offset: "2px";
1026
+ /** Outline style */
1027
+ readonly style: "solid";
1028
+ };
1029
+ readonly animation: {
1030
+ /** Standard animation durations */
1031
+ readonly duration: {
1032
+ /** Fast animations like hover states */
1033
+ readonly fast: "150ms";
1034
+ /** Base duration for most UI animations */
1035
+ readonly base: "200ms";
1036
+ /** Slower animations for larger movements */
1037
+ readonly slow: "300ms";
1038
+ };
1039
+ /** Reduced motion durations (instant or near-instant) */
1040
+ readonly reducedMotion: {
1041
+ readonly fast: "0ms";
1042
+ readonly base: "0ms";
1043
+ readonly slow: "0ms";
1044
+ };
1045
+ };
967
1046
  };
968
1047
 
969
- export { borderRadius, boxShadow, brandColors, breakpoints, colors, tokens as default, fontFamily, fontSize, fontWeight, layoutColors, semanticColors, spacing, statusColors, tokens, transition, zIndex };
1048
+ export { animation, borderRadius, boxShadow, brandColors, breakpoints, colors, tokens as default, focusRing, fontFamily, fontSize, fontWeight, layoutColors, semanticColors, spacing, statusColors, tokens, transition, zIndex };
package/dist/index.js CHANGED
@@ -20,12 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ animation: () => animation,
23
24
  borderRadius: () => borderRadius,
24
25
  boxShadow: () => boxShadow,
25
26
  brandColors: () => brandColors,
26
27
  breakpoints: () => breakpoints,
27
28
  colors: () => colors,
28
29
  default: () => index_default,
30
+ focusRing: () => focusRing,
29
31
  fontFamily: () => fontFamily,
30
32
  fontSize: () => fontSize,
31
33
  fontWeight: () => fontWeight,
@@ -296,8 +298,8 @@ var brandColors = {
296
298
  var statusColors = {
297
299
  success: "#10b981",
298
300
  // Green - positive actions
299
- warning: "#f59e0b",
300
- // Amber - warnings/cautions
301
+ warning: "#d97706",
302
+ // Amber - warnings/cautions (4.5:1 contrast ratio for WCAG AA)
301
303
  error: "#ef4444",
302
304
  // Red - errors/destructive
303
305
  info: "#3b82f6"
@@ -666,6 +668,31 @@ var transition = {
666
668
  linear: "linear"
667
669
  }
668
670
  };
671
+ var focusRing = {
672
+ /** Width of the focus ring outline */
673
+ width: "2px",
674
+ /** Offset between the element and the focus ring */
675
+ offset: "2px",
676
+ /** Outline style */
677
+ style: "solid"
678
+ };
679
+ var animation = {
680
+ /** Standard animation durations */
681
+ duration: {
682
+ /** Fast animations like hover states */
683
+ fast: "150ms",
684
+ /** Base duration for most UI animations */
685
+ base: "200ms",
686
+ /** Slower animations for larger movements */
687
+ slow: "300ms"
688
+ },
689
+ /** Reduced motion durations (instant or near-instant) */
690
+ reducedMotion: {
691
+ fast: "0ms",
692
+ base: "0ms",
693
+ slow: "0ms"
694
+ }
695
+ };
669
696
  var tokens = {
670
697
  colors,
671
698
  brandColors,
@@ -680,16 +707,20 @@ var tokens = {
680
707
  boxShadow,
681
708
  zIndex,
682
709
  breakpoints,
683
- transition
710
+ transition,
711
+ focusRing,
712
+ animation
684
713
  };
685
714
  var index_default = tokens;
686
715
  // Annotate the CommonJS export names for ESM import in node:
687
716
  0 && (module.exports = {
717
+ animation,
688
718
  borderRadius,
689
719
  boxShadow,
690
720
  brandColors,
691
721
  breakpoints,
692
722
  colors,
723
+ focusRing,
693
724
  fontFamily,
694
725
  fontSize,
695
726
  fontWeight,
package/dist/index.mjs CHANGED
@@ -257,8 +257,8 @@ var brandColors = {
257
257
  var statusColors = {
258
258
  success: "#10b981",
259
259
  // Green - positive actions
260
- warning: "#f59e0b",
261
- // Amber - warnings/cautions
260
+ warning: "#d97706",
261
+ // Amber - warnings/cautions (4.5:1 contrast ratio for WCAG AA)
262
262
  error: "#ef4444",
263
263
  // Red - errors/destructive
264
264
  info: "#3b82f6"
@@ -627,6 +627,31 @@ var transition = {
627
627
  linear: "linear"
628
628
  }
629
629
  };
630
+ var focusRing = {
631
+ /** Width of the focus ring outline */
632
+ width: "2px",
633
+ /** Offset between the element and the focus ring */
634
+ offset: "2px",
635
+ /** Outline style */
636
+ style: "solid"
637
+ };
638
+ var animation = {
639
+ /** Standard animation durations */
640
+ duration: {
641
+ /** Fast animations like hover states */
642
+ fast: "150ms",
643
+ /** Base duration for most UI animations */
644
+ base: "200ms",
645
+ /** Slower animations for larger movements */
646
+ slow: "300ms"
647
+ },
648
+ /** Reduced motion durations (instant or near-instant) */
649
+ reducedMotion: {
650
+ fast: "0ms",
651
+ base: "0ms",
652
+ slow: "0ms"
653
+ }
654
+ };
630
655
  var tokens = {
631
656
  colors,
632
657
  brandColors,
@@ -641,16 +666,20 @@ var tokens = {
641
666
  boxShadow,
642
667
  zIndex,
643
668
  breakpoints,
644
- transition
669
+ transition,
670
+ focusRing,
671
+ animation
645
672
  };
646
673
  var index_default = tokens;
647
674
  export {
675
+ animation,
648
676
  borderRadius,
649
677
  boxShadow,
650
678
  brandColors,
651
679
  breakpoints,
652
680
  colors,
653
681
  index_default as default,
682
+ focusRing,
654
683
  fontFamily,
655
684
  fontSize,
656
685
  fontWeight,
package/dist/tokens.css CHANGED
@@ -44,7 +44,7 @@
44
44
  /* Status Colors */
45
45
  --success: 160 84% 39%;
46
46
  --success-foreground: 0 0% 100%;
47
- --warning: 38 92% 50%;
47
+ --warning: 32 95% 44%;
48
48
  --warning-foreground: 0 0% 100%;
49
49
  --info: 217 91% 60%;
50
50
  --info-foreground: 0 0% 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-tokens",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "description": "Design tokens for the Classic theme system",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -11,6 +11,11 @@
11
11
  "import": "./dist/index.mjs",
12
12
  "require": "./dist/index.js"
13
13
  },
14
+ "./contrast": {
15
+ "types": "./dist/contrast.d.ts",
16
+ "import": "./dist/contrast.mjs",
17
+ "require": "./dist/contrast.js"
18
+ },
14
19
  "./css": "./dist/tokens.css"
15
20
  },
16
21
  "sideEffects": [
@@ -20,8 +25,8 @@
20
25
  "dist"
21
26
  ],
22
27
  "scripts": {
23
- "build": "tsup src/index.ts --format esm,cjs --dts && node scripts/generate-css.js",
24
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
28
+ "build": "tsup src/index.ts src/contrast.ts --format esm,cjs --dts && node scripts/generate-css.js",
29
+ "dev": "tsup src/index.ts src/contrast.ts --format esm,cjs --dts --watch",
25
30
  "clean": "rm -rf dist"
26
31
  },
27
32
  "keywords": [