ui-foundations 0.4.1 → 0.7.0

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.
Files changed (61) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +49 -13
  3. package/dist/core/index.css +1 -0
  4. package/dist/macros/ui.njk +94 -0
  5. package/dist/main.css +723 -138
  6. package/dist/react/accordion.js +36 -0
  7. package/dist/react/avatar.js +34 -0
  8. package/dist/react/button.js +1 -12
  9. package/dist/react/checkbox.js +3 -20
  10. package/dist/react/divider.js +31 -0
  11. package/dist/react/form.js +111 -0
  12. package/dist/react/icon.js +1 -12
  13. package/dist/react/index.js +7 -0
  14. package/dist/react/input.js +7 -0
  15. package/dist/react/radio.js +3 -20
  16. package/dist/react/switch.js +3 -20
  17. package/dist/react/tabs.js +72 -0
  18. package/dist/react/textarea.js +45 -0
  19. package/dist/react/tooltip.js +25 -0
  20. package/dist/react/warn-dev.js +15 -0
  21. package/dist/tokens/css/appearance-modes.tokens.mode-dark.css +12 -6
  22. package/dist/tokens/css/appearance-modes.tokens.mode-light.css +7 -1
  23. package/dist/tokens/css/components-ui.tokens.css +93 -73
  24. package/dist/tokens/css/core-primitives.tokens.css +72 -1
  25. package/dist/tokens/css/semantics-roles.tokens.css +1 -1
  26. package/dist/tokens/css/themes-brands.tokens.brand-a.css +11 -11
  27. package/dist/tokens/css/themes-brands.tokens.brand-b.css +1 -1
  28. package/dist/tokens/css/themes-brands.tokens.brand-c.css +22 -0
  29. package/dist/tokens/json/appearance-modes.tokens.mode-dark.json +24 -0
  30. package/dist/tokens/json/appearance-modes.tokens.mode-light.json +24 -0
  31. package/dist/tokens/json/components-ui.tokens.json +403 -269
  32. package/dist/tokens/json/core-primitives.tokens.json +302 -0
  33. package/dist/tokens/json/themes-brands.tokens.brand-a.json +10 -10
  34. package/dist/tokens/json/themes-brands.tokens.brand-b.json +10 -10
  35. package/dist/tokens/json/themes-brands.tokens.brand-c.json +81 -0
  36. package/dist/tokens/tokens.yaml +2138 -578
  37. package/dist/tokens/ts/appearance-modes.tokens.mode-dark.ts +12 -6
  38. package/dist/tokens/ts/appearance-modes.tokens.mode-light.ts +7 -1
  39. package/dist/tokens/ts/components-ui.tokens.ts +94 -74
  40. package/dist/tokens/ts/core-primitives.tokens.ts +73 -2
  41. package/dist/tokens/ts/semantics-roles.tokens.ts +1 -1
  42. package/dist/tokens/ts/themes-brands.tokens.brand-a.ts +11 -11
  43. package/dist/tokens/ts/themes-brands.tokens.brand-b.ts +1 -1
  44. package/dist/tokens/ts/themes-brands.tokens.brand-c.ts +32 -0
  45. package/dist/ui/index.css +7 -0
  46. package/dist/ui/patterns/accordion.css +81 -0
  47. package/dist/ui/patterns/avatar.css +57 -0
  48. package/dist/ui/patterns/button.css +3 -3
  49. package/dist/ui/patterns/checkbox.css +28 -28
  50. package/dist/ui/patterns/divider.css +25 -0
  51. package/dist/ui/patterns/form.css +112 -0
  52. package/dist/ui/patterns/input.css +12 -12
  53. package/dist/ui/patterns/label.css +1 -1
  54. package/dist/ui/patterns/tabs.css +71 -0
  55. package/dist/ui/patterns/textarea.css +50 -0
  56. package/dist/ui/patterns/tooltip.css +64 -0
  57. package/docs/agentic/README.md +1 -0
  58. package/docs/agentic/skills/component-accessibility-audit.md +132 -0
  59. package/docs/foundations/foundation-007-typography-selectors-and-specificity.md +1 -1
  60. package/package.json +15 -3
  61. package/dist/tokens/missing-tokens.json +0 -43
@@ -7,12 +7,12 @@
7
7
  font-weight: var(--typography-label-font-weight);
8
8
  font-size: var(--typography-label-font-size);
9
9
  line-height: var(--typography-label-line-height);
10
- color: var(--input-checkbox-text-color-default);
10
+ color: var(--checkbox-text-color-default);
11
11
  cursor: pointer;
12
12
  }
13
13
 
14
14
  .checkbox-field.is-disabled {
15
- color: var(--input-checkbox-text-color-disabled);
15
+ color: var(--checkbox-text-color-disabled);
16
16
  cursor: not-allowed;
17
17
  }
18
18
 
@@ -25,10 +25,10 @@
25
25
  place-content: center;
26
26
  border-style: solid;
27
27
  border-width: var(--checkbox-border-size-default);
28
- border-color: var(--input-checkbox-border-color-default);
28
+ border-color: var(--checkbox-border-color-default);
29
29
  border-radius: var(--corner-checkbox-radius);
30
- background: var(--input-checkbox-container-background-default);
31
- color: var(--input-checkbox-text-color-active);
30
+ background: var(--checkbox-container-background-default);
31
+ color: var(--checkbox-text-color-active);
32
32
  cursor: pointer;
33
33
  }
34
34
 
@@ -39,8 +39,8 @@
39
39
 
40
40
  .checkbox:checked,
41
41
  .checkbox.is-checked {
42
- border-color: var(--input-checkbox-border-color-active);
43
- background: var(--input-checkbox-container-background-active);
42
+ border-color: var(--checkbox-border-color-active);
43
+ background: var(--checkbox-container-background-active);
44
44
  }
45
45
 
46
46
  .checkbox:checked::after,
@@ -61,8 +61,8 @@
61
61
 
62
62
  .checkbox:indeterminate,
63
63
  .checkbox.is-indeterminate {
64
- border-color: var(--input-checkbox-border-color-active);
65
- background: var(--input-checkbox-container-background-active);
64
+ border-color: var(--checkbox-border-color-active);
65
+ background: var(--checkbox-container-background-active);
66
66
  }
67
67
 
68
68
  .checkbox:indeterminate::after,
@@ -77,9 +77,9 @@
77
77
  .checkbox.is-hover {
78
78
  background:
79
79
  linear-gradient(0deg, var(--color-overlay-hover), var(--color-overlay-hover)),
80
- var(--input-checkbox-container-background-hover);
81
- color: var(--input-checkbox-text-color-hover);
82
- border-color: var(--input-checkbox-border-color-hover);
80
+ var(--checkbox-container-background-hover);
81
+ color: var(--checkbox-text-color-hover);
82
+ border-color: var(--checkbox-border-color-hover);
83
83
  border-width: var(--checkbox-border-size-hover);
84
84
  }
85
85
 
@@ -87,9 +87,9 @@
87
87
  .checkbox.is-checked.is-hover {
88
88
  background:
89
89
  linear-gradient(0deg, var(--color-overlay-hover), var(--color-overlay-hover)),
90
- var(--input-checkbox-container-background-active);
91
- color: var(--input-checkbox-text-color-hover);
92
- border-color: var(--input-checkbox-border-color-hover);
90
+ var(--checkbox-container-background-active);
91
+ color: var(--checkbox-text-color-hover);
92
+ border-color: var(--checkbox-border-color-hover);
93
93
  border-width: var(--checkbox-border-size-hover);
94
94
  }
95
95
 
@@ -97,9 +97,9 @@
97
97
  .checkbox.is-indeterminate.is-hover {
98
98
  background:
99
99
  linear-gradient(0deg, var(--color-overlay-hover), var(--color-overlay-hover)),
100
- var(--input-checkbox-container-background-active);
101
- color: var(--input-checkbox-text-color-hover);
102
- border-color: var(--input-checkbox-border-color-hover);
100
+ var(--checkbox-container-background-active);
101
+ color: var(--checkbox-text-color-hover);
102
+ border-color: var(--checkbox-border-color-hover);
103
103
  border-width: var(--checkbox-border-size-hover);
104
104
  }
105
105
 
@@ -107,8 +107,8 @@
107
107
  .checkbox.is-active {
108
108
  background:
109
109
  linear-gradient(0deg, var(--color-overlay-active), var(--color-overlay-active)),
110
- var(--input-checkbox-container-background-default);
111
- border-color: var(--input-checkbox-border-color-active);
110
+ var(--checkbox-container-background-default);
111
+ border-color: var(--checkbox-border-color-active);
112
112
  border-width: var(--size-border-200);
113
113
  }
114
114
 
@@ -116,21 +116,21 @@
116
116
  .checkbox.is-checked.is-active {
117
117
  background:
118
118
  linear-gradient(0deg, var(--color-overlay-active), var(--color-overlay-active)),
119
- var(--input-checkbox-container-background-active);
120
- border-color: var(--input-checkbox-border-color-active);
119
+ var(--checkbox-container-background-active);
120
+ border-color: var(--checkbox-border-color-active);
121
121
  }
122
122
 
123
123
  .checkbox:indeterminate:active,
124
124
  .checkbox.is-indeterminate.is-active {
125
125
  background:
126
126
  linear-gradient(0deg, var(--color-overlay-active), var(--color-overlay-active)),
127
- var(--input-checkbox-container-background-active);
128
- border-color: var(--input-checkbox-border-color-active);
127
+ var(--checkbox-container-background-active);
128
+ border-color: var(--checkbox-border-color-active);
129
129
  }
130
130
 
131
131
  .checkbox:focus-visible,
132
132
  .checkbox.is-focus-visible {
133
- border-color: var(--input-checkbox-border-color-focus);
133
+ border-color: var(--checkbox-border-color-focus);
134
134
  outline: none;
135
135
  box-shadow: 0 0 0 var(--shadow-focus, 0) var(--color-focus, transparent);
136
136
  }
@@ -138,9 +138,9 @@
138
138
  .checkbox:disabled,
139
139
  .checkbox.is-disabled {
140
140
  border-width: var(--checkbox-border-size-disabled);
141
- border-color: var(--input-checkbox-border-color-disabled);
142
- background: var(--input-checkbox-container-background-disabled);
143
- color: var(--input-checkbox-text-color-disabled);
141
+ border-color: var(--checkbox-border-color-disabled);
142
+ background: var(--checkbox-container-background-disabled);
143
+ color: var(--checkbox-text-color-disabled);
144
144
  cursor: not-allowed;
145
145
  }
146
146
  }
@@ -0,0 +1,25 @@
1
+ @layer components {
2
+ .divider {
3
+ border: none;
4
+ margin: 0;
5
+ padding: 0;
6
+ background: var(--divider-color, var(--color-border-default));
7
+ block-size: var(--size-border-100);
8
+ inline-size: 100%;
9
+ align-self: stretch;
10
+ }
11
+
12
+ /* ── Vertical orientation ── */
13
+
14
+ .divider[aria-orientation="vertical"] {
15
+ block-size: auto;
16
+ inline-size: var(--size-border-100);
17
+ align-self: stretch;
18
+ }
19
+
20
+ /* ── Subtle variant ── */
21
+
22
+ .divider.subtle {
23
+ --divider-color: var(--color-border-subtle);
24
+ }
25
+ }
@@ -0,0 +1,112 @@
1
+ @layer components {
2
+ /* ----------------------------------------------------------------
3
+ * Form — layout container for form fields
4
+ * ---------------------------------------------------------------- */
5
+
6
+ .form {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--form-gap);
10
+ padding-inline: var(--form-padding-inline);
11
+ padding-block: var(--form-padding-block);
12
+ background: var(--form-container-background);
13
+ border: var(--form-border-size) solid var(--form-container-border-color);
14
+ border-radius: var(--form-border-radius);
15
+ }
16
+
17
+ .form.borderless {
18
+ border: none;
19
+ padding: 0;
20
+ background: transparent;
21
+ }
22
+
23
+ /* ----------------------------------------------------------------
24
+ * Form Group — groups related fields with an optional title
25
+ * ---------------------------------------------------------------- */
26
+
27
+ .form-group {
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: var(--form-group-gap);
31
+ margin: 0;
32
+ padding: 0;
33
+ border: 0;
34
+ min-inline-size: 0;
35
+ }
36
+
37
+ .form-group__title {
38
+ font-family: var(--typography-heading-font-family);
39
+ font-weight: var(--typography-heading-font-weight);
40
+ font-size: var(--typography-heading-font-size-sm);
41
+ line-height: var(--typography-heading-line-height-sm);
42
+ color: var(--form-group-title-color);
43
+ margin: 0;
44
+ }
45
+
46
+ /* ----------------------------------------------------------------
47
+ * Form Field — single field with label, input, and helper/error
48
+ * ---------------------------------------------------------------- */
49
+
50
+ .form-field {
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: var(--form-field-gap);
54
+ }
55
+
56
+ .form-field[data-label-position="side"] {
57
+ flex-direction: row;
58
+ align-items: flex-start;
59
+ }
60
+
61
+ .form-field[data-label-position="side"] > .field-label {
62
+ flex-shrink: 0;
63
+ inline-size: 8rem;
64
+ padding-block-start: var(--size-spacing-200);
65
+ }
66
+
67
+ .form-field[data-label-position="side"] > .form-field__body {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: var(--form-field-gap);
72
+ }
73
+
74
+ /* ----------------------------------------------------------------
75
+ * Helper and error text
76
+ * ---------------------------------------------------------------- */
77
+
78
+ .form-field__helper {
79
+ font-family: var(--typography-body-font-family);
80
+ font-size: var(--font-size-sm);
81
+ line-height: var(--line-height-sm);
82
+ color: var(--form-field-helper-text-color-default);
83
+ margin: 0;
84
+ }
85
+
86
+ .form-field.is-invalid .form-field__helper {
87
+ color: var(--form-field-helper-text-color-invalid);
88
+ }
89
+
90
+ /* ----------------------------------------------------------------
91
+ * Form Actions — button area at the bottom
92
+ * ---------------------------------------------------------------- */
93
+
94
+ .form-actions {
95
+ display: flex;
96
+ gap: var(--size-spacing-200);
97
+ justify-content: flex-end;
98
+ padding-block-start: var(--size-spacing-200);
99
+ }
100
+
101
+ .form-actions[data-align="start"] {
102
+ justify-content: flex-start;
103
+ }
104
+
105
+ .form-actions[data-align="stretch"] {
106
+ flex-direction: column;
107
+ }
108
+
109
+ .form-actions[data-align="stretch"] > * {
110
+ inline-size: 100%;
111
+ }
112
+ }
@@ -7,25 +7,25 @@
7
7
  font-weight: var(--input-font-weight);
8
8
  font-size: var(--input-font-size);
9
9
  line-height: var(--input-line-height, var(--button-line-height, 1.5));
10
- color: var(--input-text-text-color-default);
11
- background: var(--input-text-container-background-default);
10
+ color: var(--input-text-color-default);
11
+ background: var(--input-container-background-default);
12
12
  border-style: solid;
13
13
  border-width: var(--input-border-size-default);
14
- border-color: var(--input-text-border-color-default);
14
+ border-color: var(--input-border-color-default);
15
15
  border-radius: var(--input-border-radius);
16
16
  padding-inline: var(--input-padding-inline);
17
17
  padding-block: var(--input-padding-block);
18
18
  }
19
19
 
20
20
  .input::placeholder {
21
- color: var(--input-text-text-color-placeholder);
21
+ color: var(--input-text-color-placeholder);
22
22
  }
23
23
 
24
24
  .input:hover,
25
25
  .input.is-hover {
26
26
  background:
27
27
  linear-gradient(0deg, var(--input-overlay-hover), var(--input-overlay-hover)),
28
- var(--input-text-container-background-default);
28
+ var(--input-container-background-default);
29
29
  border-width: var(--input-border-size-hover);
30
30
  padding-inline: calc(
31
31
  var(--input-padding-inline) + var(--input-border-size-default) -
@@ -35,15 +35,15 @@
35
35
  var(--input-padding-block) + var(--input-border-size-default) -
36
36
  var(--input-border-size-hover)
37
37
  );
38
- border-color: var(--input-text-border-color-hover);
39
- color: var(--input-text-text-color-hover);
38
+ border-color: var(--input-border-color-hover);
39
+ color: var(--input-text-color-hover);
40
40
  }
41
41
 
42
42
  .input:active,
43
43
  .input.is-active {
44
44
  background:
45
45
  linear-gradient(0deg, var(--input-overlay-active), var(--input-overlay-active)),
46
- var(--input-text-container-background-default);
46
+ var(--input-container-background-default);
47
47
  border-width: var(--input-border-size-active);
48
48
  padding-inline: calc(
49
49
  var(--input-padding-inline) + var(--input-border-size-default) -
@@ -53,14 +53,14 @@
53
53
  var(--input-padding-block) + var(--input-border-size-default) -
54
54
  var(--input-border-size-active)
55
55
  );
56
- border-color: var(--input-text-border-color-active);
57
- color: var(--input-text-text-color-active);
56
+ border-color: var(--input-border-color-active);
57
+ color: var(--input-text-color-active);
58
58
  }
59
59
 
60
60
  .input:focus-visible,
61
61
  .input.is-focus-visible {
62
- background: var(--input-text-container-background-focus);
63
- border-color: var(--input-text-border-color-focus);
62
+ background: var(--input-container-background-focus);
63
+ border-color: var(--input-border-color-focus);
64
64
  outline: none;
65
65
  box-shadow: 0 0 0 var(--shadow-focus, 0) var(--color-focus, transparent);
66
66
  }
@@ -2,7 +2,7 @@
2
2
  .label-content {
3
3
  display: inline-flex;
4
4
  align-items: center;
5
- gap: var(--label-gap, 0.5em);
5
+ gap: var(--typography-label-gap, 0.5em);
6
6
  line-height: inherit;
7
7
  }
8
8
 
@@ -0,0 +1,71 @@
1
+ @layer components {
2
+ .tabs {
3
+ display: flex;
4
+ flex-direction: column;
5
+ }
6
+
7
+ .tab-list {
8
+ display: flex;
9
+ gap: 0;
10
+ border-block-end: var(--size-border-100) solid var(--color-border-default);
11
+ }
12
+
13
+ .tab-list[aria-orientation="vertical"] {
14
+ flex-direction: column;
15
+ border-block-end: none;
16
+ border-inline-end: var(--size-border-100) solid var(--color-border-default);
17
+ }
18
+
19
+ .tab {
20
+ display: inline-flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ padding-inline: var(--size-spacing-400);
24
+ padding-block: var(--size-spacing-300);
25
+ font-family: var(--font-family-sans);
26
+ font-size: var(--font-size-md);
27
+ font-weight: var(--font-weight-500);
28
+ line-height: var(--line-height-md);
29
+ color: var(--color-text-default);
30
+ background: transparent;
31
+ border: none;
32
+ border-block-end: var(--size-border-200) solid transparent;
33
+ cursor: pointer;
34
+ white-space: nowrap;
35
+ transition: border-color 0.15s ease, color 0.15s ease;
36
+ }
37
+
38
+ .tab:hover,
39
+ .tab.is-hover {
40
+ color: var(--color-text-brand);
41
+ border-block-end-color: var(--color-border-subtle);
42
+ }
43
+
44
+ .tab[aria-selected="true"],
45
+ .tab.is-selected {
46
+ color: var(--color-text-brand);
47
+ font-weight: var(--font-weight-600);
48
+ border-block-end-color: var(--color-border-brand);
49
+ }
50
+
51
+ .tab:focus-visible,
52
+ .tab.is-focus-visible {
53
+ outline: none;
54
+ box-shadow: inset 0 0 0 var(--shadow-focus) var(--color-focus);
55
+ }
56
+
57
+ .tab:disabled,
58
+ .tab.is-disabled {
59
+ color: var(--color-text-disabled);
60
+ cursor: not-allowed;
61
+ border-block-end-color: transparent;
62
+ }
63
+
64
+ .tab-panel {
65
+ padding-block: var(--size-spacing-400);
66
+ }
67
+
68
+ .tab-panel[hidden] {
69
+ display: none;
70
+ }
71
+ }
@@ -0,0 +1,50 @@
1
+ @layer components {
2
+ .textarea {
3
+ display: block;
4
+ inline-size: 100%;
5
+ min-block-size: calc(var(--line-height-md) * 3 + var(--size-spacing-300) * 2);
6
+ padding-inline: var(--size-spacing-300);
7
+ padding-block: var(--size-spacing-300);
8
+ font-family: var(--font-family-sans);
9
+ font-size: var(--font-size-md);
10
+ line-height: var(--line-height-md);
11
+ color: var(--color-text-default);
12
+ background: var(--color-fill-surface);
13
+ border: var(--size-border-100) solid var(--color-border-default);
14
+ border-radius: var(--size-radius-300);
15
+ resize: vertical;
16
+ transition: border-color 0.15s ease;
17
+ }
18
+
19
+ .textarea::placeholder {
20
+ color: var(--color-text-disabled);
21
+ }
22
+
23
+ .textarea:hover,
24
+ .textarea.is-hover {
25
+ border-color: var(--color-border-strong);
26
+ }
27
+
28
+ .textarea:focus-visible,
29
+ .textarea.is-focus-visible {
30
+ border-color: var(--color-focus);
31
+ outline: none;
32
+ box-shadow: 0 0 0 var(--shadow-focus) var(--color-focus);
33
+ }
34
+
35
+ .textarea:disabled,
36
+ .textarea.is-disabled {
37
+ color: var(--color-text-disabled);
38
+ background: var(--color-fill-disabled);
39
+ border-color: var(--color-border-disabled);
40
+ cursor: not-allowed;
41
+ resize: none;
42
+ }
43
+
44
+ .textarea[readonly] {
45
+ background: var(--color-fill-surface);
46
+ border-color: var(--color-border-default);
47
+ cursor: default;
48
+ resize: none;
49
+ }
50
+ }
@@ -0,0 +1,64 @@
1
+ @layer components {
2
+ .tooltip {
3
+ position: absolute;
4
+ z-index: var(--zindex-tooltip);
5
+ max-inline-size: 15rem;
6
+ padding-inline: var(--size-spacing-300);
7
+ padding-block: var(--size-spacing-200);
8
+ font-family: var(--font-family-sans);
9
+ font-size: var(--font-size-sm);
10
+ line-height: var(--line-height-sm);
11
+ color: var(--color-text-inverse);
12
+ background: var(--color-neutral-900);
13
+ border-radius: var(--size-radius-300);
14
+ pointer-events: none;
15
+ opacity: 0;
16
+ transition: opacity 0.15s ease;
17
+ }
18
+
19
+ .tooltip.is-visible {
20
+ opacity: 1;
21
+ }
22
+
23
+ /* ── Placement ── */
24
+
25
+ .tooltip[data-placement="top"] {
26
+ inset-block-end: 100%;
27
+ inset-inline-start: 50%;
28
+ transform: translateX(-50%);
29
+ margin-block-end: var(--size-spacing-200);
30
+ }
31
+
32
+ .tooltip[data-placement="bottom"] {
33
+ inset-block-start: 100%;
34
+ inset-inline-start: 50%;
35
+ transform: translateX(-50%);
36
+ margin-block-start: var(--size-spacing-200);
37
+ }
38
+
39
+ .tooltip[data-placement="left"] {
40
+ inset-inline-end: 100%;
41
+ inset-block-start: 50%;
42
+ transform: translateY(-50%);
43
+ margin-inline-end: var(--size-spacing-200);
44
+ }
45
+
46
+ .tooltip[data-placement="right"] {
47
+ inset-inline-start: 100%;
48
+ inset-block-start: 50%;
49
+ transform: translateY(-50%);
50
+ margin-inline-start: var(--size-spacing-200);
51
+ }
52
+
53
+ /* ── Trigger wrapper ── */
54
+
55
+ .tooltip-trigger {
56
+ position: relative;
57
+ display: inline-flex;
58
+ }
59
+
60
+ .tooltip-trigger:hover > .tooltip,
61
+ .tooltip-trigger:focus-within > .tooltip {
62
+ opacity: 1;
63
+ }
64
+ }
@@ -32,3 +32,4 @@ in the repository.
32
32
  ## Skills
33
33
 
34
34
  - `skills/ux-writing-coach.md` — multilingual UX writing review skill for product copy
35
+ - `skills/component-accessibility-audit.md` — repeatable workflow and template for single-component accessibility audits
@@ -0,0 +1,132 @@
1
+ # Component Accessibility Audit Skill
2
+
3
+ ## Purpose
4
+
5
+ Create a repeatable, component-level accessibility audit workflow before implementation fixes.
6
+
7
+ Use this skill to evaluate one interactive component at a time and produce findings in a consistent format that can drive small follow-up PRs.
8
+
9
+ ## Governance references (must read first)
10
+
11
+ 1. `DESIGN.md`
12
+ 2. `AGENTS.md`
13
+ 3. `docs/playbook.md`
14
+ 4. `docs/working-context.md`
15
+ 5. `docs/ui-foundations-rules.md`
16
+ 6. `docs/foundations/`
17
+ 7. `docs/agentic/assistant-behavior-rules.md`
18
+ 8. `IMPLEMENTATION.md`
19
+
20
+ Never contradict these sources. Prefer explicit, verifiable findings over assumptions.
21
+
22
+ ## Scope
23
+
24
+ Apply this skill to interactive components (e.g., Button, Link, Input, Select, Checkbox, Radio, Switch, Tabs, Accordion, Modal, Flyout, Dropdown, Tooltip, Pagination, Show More/Show Less, Tags).
25
+
26
+ Do not use this skill to implement broad fixes in the same PR. This skill is audit-first.
27
+
28
+ ## Audit workflow (repeatable)
29
+
30
+ 1. **Plan**
31
+ - Select exactly one component target.
32
+ - Confirm component surfaces in scope (CSS pattern, macro/template, React wrapper, docs, tests).
33
+
34
+ 2. **Collect evidence**
35
+ - Inspect source implementation files.
36
+ - Inspect usage examples and docs.
37
+ - Inspect test coverage.
38
+ - Record exact file paths and relevant lines.
39
+
40
+ 3. **Run checks**
41
+ - Semantic HTML
42
+ - Accessible names
43
+ - Keyboard behavior
44
+ - Focus management
45
+ - ARIA/state handling
46
+ - Form association (where relevant)
47
+ - Icon-only usage
48
+ - Contrast/token risks
49
+ - Test coverage
50
+ - Documentation gaps
51
+ - Manual screen reader validation needs
52
+
53
+ 4. **Score severity**
54
+ - Assign one severity per finding using the model below.
55
+
56
+ 5. **Propose minimal fix**
57
+ - Suggest the smallest safe change that resolves the finding.
58
+ - Keep fixes scoped to one component per follow-up PR.
59
+
60
+ 6. **Report**
61
+ - Output findings using the template in this document.
62
+
63
+ ## Severity model
64
+
65
+ Use one of these labels:
66
+
67
+ - **blocker**
68
+ - Breaks core accessibility behavior (e.g., no accessible name, unusable keyboard path, broken form semantics, missing focus trap in modal).
69
+ - Should be fixed before release.
70
+
71
+ - **high**
72
+ - Major accessibility risk with likely user impact, but possible workaround exists.
73
+ - Prioritize immediately after blockers.
74
+
75
+ - **medium**
76
+ - Important gap or drift that reduces accessibility quality/reliability.
77
+ - Schedule in near-term follow-up.
78
+
79
+ - **low**
80
+ - Minor issue, documentation gap, or optimization with limited immediate impact.
81
+ - Track and resolve in routine improvements.
82
+
83
+ ## Findings template
84
+
85
+ Use this exact structure for each finding:
86
+
87
+ - **Component:**
88
+ - **Check area:** (semantic HTML / accessible name / keyboard / focus / ARIA-state / form association / icon-only / contrast-token / tests / docs / manual SR)
89
+ - **Observed behavior:**
90
+ - **Expected behavior:**
91
+ - **Evidence:** (file paths + line refs)
92
+ - **Severity:** blocker | high | medium | low
93
+ - **Minimal fix strategy:**
94
+ - **Follow-up test need:**
95
+ - **Manual SR validation need:**
96
+
97
+ ## Minimal-fix strategy
98
+
99
+ When proposing a fix:
100
+
101
+ 1. Prefer native semantics over custom ARIA.
102
+ 2. Change the smallest surface that safely resolves the issue.
103
+ 3. Keep API compatibility unless current API is inherently inaccessible.
104
+ 4. Update docs and tests in the same small PR when behavior changes.
105
+ 5. Avoid cross-component refactors in accessibility fix PRs.
106
+
107
+ ## Manual screen reader validation guidance
108
+
109
+ For each audited component, explicitly state whether manual SR validation is needed and why.
110
+
111
+ Recommended baseline matrix:
112
+ - VoiceOver + Safari (macOS)
113
+ - NVDA + Firefox (Windows)
114
+ - Optional: JAWS + Chrome for enterprise parity
115
+
116
+ Validate at minimum:
117
+ - role announcement
118
+ - name announcement
119
+ - state announcement
120
+ - focus order
121
+ - action feedback after interaction
122
+
123
+ ## Example audit prompt (single component)
124
+
125
+ > Audit the `Link` component only using the Component Accessibility Audit Skill. Check semantic HTML, accessible names, keyboard behavior, focus management, ARIA/state handling, icon-only usage, contrast/token risks, tests, docs, and manual screen reader validation needs. Output findings using the required template and assign severity with blocker/high/medium/low. Do not implement fixes.
126
+
127
+ ## Output expectations
128
+
129
+ - Be explicit and evidence-based.
130
+ - Distinguish verified findings from assumptions.
131
+ - Keep findings actionable for small follow-up PRs.
132
+ - If not verified, mark as "not verified".
@@ -22,7 +22,7 @@ Provide a consistent typography API with low selector specificity and predictabl
22
22
 
23
23
  4. Use `:where()` for zero-specificity size/scale selectors where possible.
24
24
 
25
- 5. Reserve data attributes (for example `[data-theme]`, `[data-density]`) for context/state, not as primary styling API.
25
+ 5. Reserve data attributes (for example `[data-brand]`, `[data-mode]`, `[data-density]`) for context/state, not as primary styling API.
26
26
 
27
27
  ## Implications
28
28