@symbo.ls/mcp 1.0.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 (35) hide show
  1. package/.env.example +16 -0
  2. package/.env.railway +13 -0
  3. package/LICENSE +21 -0
  4. package/README.md +184 -0
  5. package/mcp.json +57 -0
  6. package/package.json +20 -0
  7. package/pyproject.toml +25 -0
  8. package/railway.toml +26 -0
  9. package/run.sh +17 -0
  10. package/symbols_mcp/__init__.py +1 -0
  11. package/symbols_mcp/server.py +1114 -0
  12. package/symbols_mcp/skills/ACCESSIBILITY.md +471 -0
  13. package/symbols_mcp/skills/ACCESSIBILITY_AUDITORY.md +70 -0
  14. package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +257 -0
  15. package/symbols_mcp/skills/BRAND_INDENTITY.md +69 -0
  16. package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +304 -0
  17. package/symbols_mcp/skills/CLAUDE.md +2158 -0
  18. package/symbols_mcp/skills/CLI_QUICK_START.md +205 -0
  19. package/symbols_mcp/skills/DESIGN_CRITIQUE.md +64 -0
  20. package/symbols_mcp/skills/DESIGN_DIRECTION.md +320 -0
  21. package/symbols_mcp/skills/DESIGN_SYSTEM_ARCHITECT.md +64 -0
  22. package/symbols_mcp/skills/DESIGN_SYSTEM_CONFIG.md +487 -0
  23. package/symbols_mcp/skills/DESIGN_SYSTEM_IN_PROPS.md +136 -0
  24. package/symbols_mcp/skills/DESIGN_TO_CODE.md +64 -0
  25. package/symbols_mcp/skills/DESIGN_TREND.md +50 -0
  26. package/symbols_mcp/skills/DOMQL_v2-v3_MIGRATION.md +236 -0
  27. package/symbols_mcp/skills/FIGMA_MATCHING.md +63 -0
  28. package/symbols_mcp/skills/GARY_TAN.md +80 -0
  29. package/symbols_mcp/skills/MARKETING_ASSETS.md +66 -0
  30. package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +614 -0
  31. package/symbols_mcp/skills/QUICKSTART.md +79 -0
  32. package/symbols_mcp/skills/SYMBOLS_LOCAL_INSTRUCTIONS.md +1405 -0
  33. package/symbols_mcp/skills/THE_PRESENTATION.md +69 -0
  34. package/symbols_mcp/skills/UI_UX_PATTERNS.md +68 -0
  35. package/windsurf-mcp-config.json +18 -0
@@ -0,0 +1,471 @@
1
+ # Accessibility in Symbols
2
+
3
+ Guide for building accessible components using Symbols v3 syntax. Every component should be usable by keyboard, screen readers, and assistive technologies.
4
+
5
+ ## Use Semantic Atoms
6
+
7
+ Always prefer built-in atom components over generic `Box` or `Div`. Atoms map to proper HTML elements with built-in accessibility semantics.
8
+
9
+ ```js
10
+ // CORRECT — semantic atoms
11
+ { Button: { text: 'Submit' } } // <button>
12
+ { Link: { text: 'Dashboard', href: '/dashboard' } } // <a>
13
+ { Input: { placeholder: 'Search...' } } // <input>
14
+ { Nav: { Link: { text: 'Home', href: '/' } } } // <nav>
15
+ { Header: {} } // <header>
16
+ { Footer: {} } // <footer>
17
+ { Main: {} } // <main>
18
+ { Section: {} } // <section>
19
+
20
+ // WRONG — loses semantics
21
+ { Box: { tag: 'div', text: 'Submit', onClick: fn } } // div is not a button
22
+ { Text: { text: 'Click here', onClick: fn } } // span is not interactive
23
+ ```
24
+
25
+ ## ARIA Attributes
26
+
27
+ Use the `attr` property to add ARIA attributes to any component.
28
+
29
+ ```js
30
+ // Landmark roles
31
+ { Box: { attr: { role: 'alert' }, text: 'Error occurred' } }
32
+ { Box: { attr: { role: 'status', 'aria-live': 'polite' }, text: '3 results found' } }
33
+
34
+ // Labels and descriptions
35
+ { Input: { attr: { 'aria-label': 'Search networks' }, placeholder: 'Search...' } }
36
+ { Button: { icon: 'x', attr: { 'aria-label': 'Close dialog' } } }
37
+ { Icon: { name: 'settings', attr: { 'aria-hidden': 'true' } } }
38
+
39
+ // State attributes
40
+ {
41
+ Button: {
42
+ attr: (el, s) => ({
43
+ 'aria-expanded': s.isOpen,
44
+ 'aria-controls': 'dropdown-menu'
45
+ }),
46
+ onClick: (e, el, s) => s.update({ isOpen: !s.isOpen })
47
+ }
48
+ }
49
+
50
+ // Dynamic live regions
51
+ {
52
+ Box: {
53
+ attr: { role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' },
54
+ text: (el, s) => `${s.count} items loaded`
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Keyboard Navigation
60
+
61
+ All interactive components must be keyboard-operable. Use `onKeydown` for custom keyboard handling.
62
+
63
+ ```js
64
+ // Custom keyboard interaction
65
+ {
66
+ extends: 'Flex',
67
+ attr: { role: 'listbox', tabindex: '0', 'aria-label': 'Select option' },
68
+ onKeydown: (e, el, s) => {
69
+ if (e.key === 'ArrowDown') {
70
+ e.preventDefault()
71
+ s.update({ activeIndex: Math.min(s.activeIndex + 1, s.items.length - 1) })
72
+ }
73
+ if (e.key === 'ArrowUp') {
74
+ e.preventDefault()
75
+ s.update({ activeIndex: Math.max(s.activeIndex - 1, 0) })
76
+ }
77
+ if (e.key === 'Enter' || e.key === ' ') {
78
+ e.preventDefault()
79
+ el.call('selectItem', s.items[s.activeIndex])
80
+ }
81
+ if (e.key === 'Escape') {
82
+ el.call('closeDropdown')
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### Tabindex rules
89
+
90
+ ```js
91
+ // Focusable (in tab order)
92
+ { Box: { attr: { tabindex: '0', role: 'button' } } }
93
+
94
+ // Focusable programmatically only (not in tab order)
95
+ { Box: { attr: { tabindex: '-1' } } }
96
+
97
+ // Never use tabindex > 0
98
+ ```
99
+
100
+ ## Focus Styles
101
+
102
+ Always provide visible focus indicators. Use `:focus-visible` to show focus only for keyboard users.
103
+
104
+ ```js
105
+ {
106
+ Button: {
107
+ theme: 'primary',
108
+ ':focus-visible': {
109
+ outline: 'solid, Y, blue .3',
110
+ outlineOffset: '2px'
111
+ },
112
+ // Remove default outline only when replacing with custom
113
+ ':focus': { outline: 'none' },
114
+ ':focus-visible': { boxShadow: '0 0 0 3px rgba(66, 153, 225, 0.5)' }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### Focus management in modals and dropdowns
120
+
121
+ ```js
122
+ // Trap focus in modal
123
+ {
124
+ attr: { role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Add network' },
125
+ onRender: (el) => {
126
+ const focusable = el.node.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
127
+ if (focusable.length) focusable[0].focus()
128
+ },
129
+ onKeydown: (e, el) => {
130
+ if (e.key === 'Tab') {
131
+ const focusable = el.node.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
132
+ const first = focusable[0]
133
+ const last = focusable[focusable.length - 1]
134
+ if (e.shiftKey && document.activeElement === first) {
135
+ e.preventDefault()
136
+ last.focus()
137
+ } else if (!e.shiftKey && document.activeElement === last) {
138
+ e.preventDefault()
139
+ first.focus()
140
+ }
141
+ }
142
+ if (e.key === 'Escape') {
143
+ el.state.root.update({ modal: null })
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## Accessible Forms
150
+
151
+ ### Labels
152
+
153
+ Every form input needs a visible label or `aria-label`.
154
+
155
+ ```js
156
+ // Visible label using HTML association
157
+ {
158
+ Label: { text: 'Email', attr: { for: 'email-input' } },
159
+ Input: { id: 'email-input', type: 'email', attr: { required: 'true' } }
160
+ }
161
+
162
+ // Hidden label for icon-only inputs
163
+ {
164
+ Input: {
165
+ type: 'search',
166
+ placeholder: 'Search...',
167
+ attr: { 'aria-label': 'Search networks' }
168
+ }
169
+ }
170
+
171
+ // Described by helper text
172
+ {
173
+ Input: {
174
+ id: 'password',
175
+ type: 'password',
176
+ attr: { 'aria-describedby': 'password-hint' }
177
+ },
178
+ P: { id: 'password-hint', text: 'Must be at least 8 characters', fontSize: 'Z1' }
179
+ }
180
+ ```
181
+
182
+ ### Error states
183
+
184
+ ```js
185
+ {
186
+ Input: {
187
+ attr: (el, s) => ({
188
+ 'aria-invalid': s.hasError ? 'true' : undefined,
189
+ 'aria-describedby': s.hasError ? 'error-msg' : undefined
190
+ }),
191
+ border: (el, s) => s.hasError ? 'red 1px solid' : 'gray 1px solid'
192
+ },
193
+ P: {
194
+ id: 'error-msg',
195
+ attr: { role: 'alert' },
196
+ color: 'red',
197
+ text: (el, s) => s.errorMessage,
198
+ hide: (el, s) => !s.hasError
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Required fields
204
+
205
+ ```js
206
+ {
207
+ Label: { text: 'Name', Span: { text: ' *', color: 'red', attr: { 'aria-hidden': 'true' } } },
208
+ Input: { attr: { required: 'true', 'aria-required': 'true' } }
209
+ }
210
+ ```
211
+
212
+ ## Images and Icons
213
+
214
+ ### Meaningful images need alt text
215
+
216
+ ```js
217
+ // Informative image
218
+ { Img: { src: 'chart.png', alt: 'Network uptime over last 30 days' } }
219
+
220
+ // Decorative image — hide from assistive tech
221
+ { Img: { src: 'decoration.png', alt: '', attr: { 'aria-hidden': 'true' } } }
222
+ ```
223
+
224
+ ### Icons
225
+
226
+ ```js
227
+ // Decorative icon next to text — hide icon
228
+ {
229
+ Button: {
230
+ Icon: { name: 'search', attr: { 'aria-hidden': 'true' } },
231
+ text: 'Search'
232
+ }
233
+ }
234
+
235
+ // Icon-only button — needs label
236
+ {
237
+ Button: {
238
+ icon: 'x',
239
+ attr: { 'aria-label': 'Close' }
240
+ }
241
+ }
242
+
243
+ // Icon conveying status — needs accessible text
244
+ {
245
+ Flex: {
246
+ Icon: { name: 'check', color: 'green', attr: { 'aria-hidden': 'true' } },
247
+ Text: { text: 'Connected' }
248
+ }
249
+ }
250
+ ```
251
+
252
+ ## Color and Contrast
253
+
254
+ ### Do not rely on color alone
255
+
256
+ ```js
257
+ // WRONG — only color indicates error
258
+ { Input: { border: 'red 1px solid' } }
259
+
260
+ // CORRECT — color + icon + text
261
+ {
262
+ Input: { border: 'red 1px solid', attr: { 'aria-invalid': 'true', 'aria-describedby': 'err' } },
263
+ Flex: {
264
+ Icon: { name: 'info', color: 'red', attr: { 'aria-hidden': 'true' } },
265
+ P: { id: 'err', text: 'This field is required', color: 'red', attr: { role: 'alert' } }
266
+ }
267
+ }
268
+ ```
269
+
270
+ ### Contrast with theme tokens
271
+
272
+ Use design system color tokens that ensure contrast. When using opacity modifiers, verify contrast remains sufficient.
273
+
274
+ ```js
275
+ // Good contrast
276
+ { color: 'title', background: 'white' }
277
+
278
+ // Risky — low opacity can fail contrast
279
+ { color: 'gray 0.3', background: 'white' } // Likely fails WCAG AA
280
+
281
+ // Use semantic color tokens that account for light/dark
282
+ { color: 'paragraph', background: 'document' }
283
+ ```
284
+
285
+ ## Accessible Lists and Tables
286
+
287
+ ```js
288
+ // Accessible list
289
+ {
290
+ Ul: {
291
+ attr: { 'aria-label': 'Network list' },
292
+ children: (el, s) => s.networks,
293
+ childrenAs: 'state',
294
+ childExtends: 'Li',
295
+ childProps: {
296
+ text: (el, s) => s.name
297
+ }
298
+ }
299
+ }
300
+
301
+ // Table with headers
302
+ {
303
+ tag: 'table',
304
+ attr: { 'aria-label': 'Validator data' },
305
+ Thead: {
306
+ tag: 'thead',
307
+ Tr: {
308
+ tag: 'tr',
309
+ Th_name: { tag: 'th', text: 'Name', attr: { scope: 'col' } },
310
+ Th_status: { tag: 'th', text: 'Status', attr: { scope: 'col' } }
311
+ }
312
+ }
313
+ }
314
+ ```
315
+
316
+ ## Loading and Dynamic Content
317
+
318
+ ```js
319
+ // Loading state announcement
320
+ {
321
+ Box: {
322
+ attr: (el, s) => ({
323
+ role: 'status',
324
+ 'aria-live': 'polite',
325
+ 'aria-busy': s.loading ? 'true' : 'false'
326
+ }),
327
+ text: (el, s) => s.loading ? 'Loading...' : `${s.items.length} items loaded`
328
+ }
329
+ }
330
+
331
+ // Skeleton/loading indicator
332
+ {
333
+ Box: {
334
+ attr: { role: 'status', 'aria-label': 'Loading content' },
335
+ hide: (el, s) => !s.loading
336
+ }
337
+ }
338
+ ```
339
+
340
+ ## Skip Navigation
341
+
342
+ Add a skip link as the first focusable element in layouts.
343
+
344
+ ```js
345
+ export const Layout = {
346
+ SkipLink: {
347
+ extends: "Link",
348
+ text: "Skip to main content",
349
+ href: "#main-content",
350
+ position: "absolute",
351
+ top: "-F",
352
+ left: "A",
353
+ background: "white",
354
+ color: "blue",
355
+ padding: "Z A",
356
+ zIndex: 100,
357
+ ":focus": { top: "A" },
358
+ },
359
+ Header: {},
360
+ Main: { id: "main-content", attr: { tabindex: "-1" } },
361
+ Footer: {},
362
+ };
363
+ ```
364
+
365
+ ## Accessible Dropdown
366
+
367
+ ```js
368
+ export const AccessibleDropdown = {
369
+ extends: "DropdownParent",
370
+ state: { isOpen: false, activeIndex: -1 },
371
+
372
+ Button: {
373
+ text: "Options",
374
+ attr: (el, s) => ({
375
+ "aria-haspopup": "listbox",
376
+ "aria-expanded": s.isOpen,
377
+ }),
378
+ Icon: { name: "chevronDown", attr: { "aria-hidden": "true" } },
379
+ },
380
+
381
+ Dropdown: {
382
+ attr: { role: "listbox", "aria-label": "Options" },
383
+ hide: (el, s) => !s.isOpen,
384
+ children: (el, s) => s.options,
385
+ childrenAs: "state",
386
+ childExtends: "Button",
387
+ childProps: {
388
+ attr: (el, s) => ({
389
+ role: "option",
390
+ "aria-selected": s.isSelected,
391
+ }),
392
+ theme: "transparent",
393
+ },
394
+ },
395
+ };
396
+ ```
397
+
398
+ ## Accessible Tabs
399
+
400
+ ```js
401
+ export const Tabs = {
402
+ state: { activeTab: 0 },
403
+
404
+ TabList: {
405
+ attr: { role: "tablist" },
406
+ flow: "x",
407
+ gap: "Z",
408
+ onKeydown: (e, el, s) => {
409
+ const tabs = el.node.querySelectorAll('[role="tab"]');
410
+ if (e.key === "ArrowRight")
411
+ s.update({ activeTab: (s.activeTab + 1) % tabs.length });
412
+ if (e.key === "ArrowLeft")
413
+ s.update({ activeTab: (s.activeTab - 1 + tabs.length) % tabs.length });
414
+ },
415
+ children: (el, s) => s.tabs,
416
+ childrenAs: "state",
417
+ childExtends: "Button",
418
+ childProps: (el, s) => ({
419
+ attr: {
420
+ role: "tab",
421
+ "aria-selected": s.parent.activeTab === s.index,
422
+ tabindex: s.parent.activeTab === s.index ? "0" : "-1",
423
+ },
424
+ onClick: (e, el, s) => s.parent.update({ activeTab: s.index }),
425
+ }),
426
+ },
427
+
428
+ TabPanel: {
429
+ attr: (el, s) => ({
430
+ role: "tabpanel",
431
+ tabindex: "0",
432
+ }),
433
+ },
434
+ };
435
+ ```
436
+
437
+ ## Motion and Reduced Motion
438
+
439
+ Respect user motion preferences with `@media (prefers-reduced-motion)`.
440
+
441
+ ```js
442
+ {
443
+ transition: 'background, defaultBezier, A',
444
+ animationDuration: 'C',
445
+
446
+ // Disable animations for users who prefer reduced motion
447
+ style: {
448
+ '@media (prefers-reduced-motion: reduce)': {
449
+ transition: 'none',
450
+ animation: 'none'
451
+ }
452
+ }
453
+ }
454
+ ```
455
+
456
+ ## Checklist
457
+
458
+ Before shipping any component, verify:
459
+
460
+ - [ ] All interactive elements are reachable by keyboard (Tab, Enter, Space, Escape, Arrows)
461
+ - [ ] Visible focus indicator on every focusable element
462
+ - [ ] Every `img` has `alt` text (or `alt=""` + `aria-hidden` for decorative)
463
+ - [ ] Icon-only buttons have `aria-label`
464
+ - [ ] Form inputs have associated labels
465
+ - [ ] Error messages use `role="alert"` and are linked via `aria-describedby`
466
+ - [ ] Color is not the only way to convey information
467
+ - [ ] Dynamic content updates use `aria-live` regions
468
+ - [ ] Modals trap focus and close on Escape
469
+ - [ ] Use semantic atoms (`Button`, `Link`, `Nav`, `Main`) over generic `Box`/`Div`
470
+ - [ ] Text contrast meets WCAG AA (4.5:1 for normal text, 3:1 for large)
471
+ - [ ] Animations respect `prefers-reduced-motion`
@@ -0,0 +1,70 @@
1
+ The Accessibility Auditor
2
+
3
+ You are an Accessibility Specialist at Apple, ensuring designs work for everyone.
4
+
5
+ Perform a comprehensive accessibility audit of this design:
6
+
7
+ [DESIGN DESCRIPTION OR UPLOADED DESIGN]
8
+
9
+ Audit against WCAG 2.2 Level AA standards:
10
+
11
+ 1. PERCEIVABLE
12
+ □ Text alternatives for images (alt text strategy)
13
+ □ Captions/transcripts for multimedia
14
+ □ Color not used as sole means of conveying information
15
+ □ Color contrast ratios:
16
+ - Normal text: 4.5:1 minimum
17
+ - Large text: 3:1 minimum
18
+ - UI components: 3:1 minimum
19
+ □ Resize text up to 200% without loss of content/functionality
20
+ □ Images of text avoided (except logos)
21
+
22
+ 2. OPERABLE
23
+ □ All functionality available from keyboard
24
+ □ No keyboard traps
25
+ □ Skip links provided for repetitive content
26
+ □ Page titles descriptive and unique
27
+ □ Focus order logical and predictable
28
+ □ Link purpose clear from context
29
+ □ Multiple ways to find pages (search, navigation, sitemap)
30
+ □ Headings and labels descriptive
31
+ □ Focus visible (minimum 2px outline, 3:1 contrast)
32
+ □ Pointer gestures have single-pointer alternatives
33
+ □ Motion animation can be disabled (prefers-reduced-motion)
34
+ □ No auto-playing audio
35
+ □ Touch targets minimum 44×44 CSS pixels
36
+
37
+ 3. UNDERSTANDABLE
38
+ □ Language of page identified
39
+ □ Language of parts identified
40
+ □ Components with same function identified consistently
41
+ □ Error identification clear
42
+ □ Error suggestions provided
43
+ □ Error prevention for legal/financial/data (confirmations/reversible)
44
+ □ Contextual help available
45
+
46
+ 4. ROBUST
47
+ □ Valid HTML/markup
48
+ □ Name, role, value available for all components
49
+ □ Status messages announced (ARIA live regions)
50
+
51
+ 5. MOBILE-SPECIFIC
52
+ □ Orientation not locked (responsive to rotation)
53
+ □ Input modalities supported (touch, mouse, keyboard, voice)
54
+ □ Placed where user can reach (thumb zone considerations)
55
+
56
+ 6. COGNITIVE ACCESSIBILITY
57
+ □ Reading level appropriate (Flesch-Kincaid Grade 8 or below)
58
+ □ Consistent navigation placement
59
+ □ Error messages plain language, no jargon
60
+ □ Time limits can be extended or eliminated
61
+ □ No flashing content (3 flashes per second maximum)
62
+
63
+ DELIVERABLES:
64
+ • Pass/fail checklist for each criterion
65
+ • Specific violations with location and severity
66
+ • Remediation recommendations with code/design solutions
67
+ • Accessibility statement template
68
+ • Testing checklist for QA team
69
+
70
+ Include screen reader navigation flow descriptions.