@seed-ship/mcp-ui-solid 6.24.0 → 6.25.1

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 (124) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +47 -0
  3. package/dist/components/ActionGroupRenderer.cjs +6 -6
  4. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  5. package/dist/components/ActionGroupRenderer.js +6 -6
  6. package/dist/components/ActionGroupRenderer.js.map +1 -1
  7. package/dist/components/AutocompleteDropdown.cjs +6 -6
  8. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  9. package/dist/components/AutocompleteDropdown.js +6 -6
  10. package/dist/components/AutocompleteDropdown.js.map +1 -1
  11. package/dist/components/AutocompleteFormField.cjs +6 -5
  12. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  13. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  14. package/dist/components/AutocompleteFormField.js +6 -5
  15. package/dist/components/AutocompleteFormField.js.map +1 -1
  16. package/dist/components/ChartJSRenderer.cjs +216 -6
  17. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  18. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  19. package/dist/components/ChartJSRenderer.js +217 -7
  20. package/dist/components/ChartJSRenderer.js.map +1 -1
  21. package/dist/components/ChatPrompt.cjs +5 -5
  22. package/dist/components/ChatPrompt.cjs.map +1 -1
  23. package/dist/components/ChatPrompt.js +5 -5
  24. package/dist/components/ChatPrompt.js.map +1 -1
  25. package/dist/components/ComponentToolbar.cjs +5 -5
  26. package/dist/components/ComponentToolbar.cjs.map +1 -1
  27. package/dist/components/ComponentToolbar.js +5 -5
  28. package/dist/components/ComponentToolbar.js.map +1 -1
  29. package/dist/components/DegradedFallback.cjs +1 -1
  30. package/dist/components/DegradedFallback.cjs.map +1 -1
  31. package/dist/components/DegradedFallback.js +1 -1
  32. package/dist/components/DegradedFallback.js.map +1 -1
  33. package/dist/components/ExpandableWrapper.cjs +10 -7
  34. package/dist/components/ExpandableWrapper.cjs.map +1 -1
  35. package/dist/components/ExpandableWrapper.d.ts.map +1 -1
  36. package/dist/components/ExpandableWrapper.js +10 -7
  37. package/dist/components/ExpandableWrapper.js.map +1 -1
  38. package/dist/components/FormFieldRenderer.cjs +13 -12
  39. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  40. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  41. package/dist/components/FormFieldRenderer.js +13 -12
  42. package/dist/components/FormFieldRenderer.js.map +1 -1
  43. package/dist/components/FormRenderer.cjs +1 -1
  44. package/dist/components/FormRenderer.cjs.map +1 -1
  45. package/dist/components/FormRenderer.js +1 -1
  46. package/dist/components/FormRenderer.js.map +1 -1
  47. package/dist/components/GenerativeUIErrorBoundary.cjs +1 -1
  48. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  49. package/dist/components/GenerativeUIErrorBoundary.js +1 -1
  50. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  51. package/dist/components/GhostText.cjs +2 -2
  52. package/dist/components/GhostText.cjs.map +1 -1
  53. package/dist/components/GhostText.js +2 -2
  54. package/dist/components/GhostText.js.map +1 -1
  55. package/dist/components/GraphRenderer.cjs +100 -29
  56. package/dist/components/GraphRenderer.cjs.map +1 -1
  57. package/dist/components/GraphRenderer.d.ts +1 -1
  58. package/dist/components/GraphRenderer.d.ts.map +1 -1
  59. package/dist/components/GraphRenderer.js +100 -29
  60. package/dist/components/GraphRenderer.js.map +1 -1
  61. package/dist/components/MissingValue.cjs +2 -2
  62. package/dist/components/MissingValue.cjs.map +1 -1
  63. package/dist/components/MissingValue.js +2 -2
  64. package/dist/components/MissingValue.js.map +1 -1
  65. package/dist/components/ModalRenderer.cjs +39 -31
  66. package/dist/components/ModalRenderer.cjs.map +1 -1
  67. package/dist/components/ModalRenderer.d.ts.map +1 -1
  68. package/dist/components/ModalRenderer.js +40 -32
  69. package/dist/components/ModalRenderer.js.map +1 -1
  70. package/dist/components/PortalDropdownMenu.cjs +6 -3
  71. package/dist/components/PortalDropdownMenu.cjs.map +1 -1
  72. package/dist/components/PortalDropdownMenu.d.ts.map +1 -1
  73. package/dist/components/PortalDropdownMenu.js +7 -4
  74. package/dist/components/PortalDropdownMenu.js.map +1 -1
  75. package/dist/components/ScratchpadPanel.cjs +38 -44
  76. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  77. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  78. package/dist/components/ScratchpadPanel.js +38 -44
  79. package/dist/components/ScratchpadPanel.js.map +1 -1
  80. package/dist/components/UIResourceRenderer.cjs +20 -20
  81. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  82. package/dist/components/UIResourceRenderer.js +20 -20
  83. package/dist/components/UIResourceRenderer.js.map +1 -1
  84. package/dist/components/chart-missing-plugin.cjs +4 -4
  85. package/dist/components/chart-missing-plugin.cjs.map +1 -1
  86. package/dist/components/chart-missing-plugin.d.ts +3 -1
  87. package/dist/components/chart-missing-plugin.d.ts.map +1 -1
  88. package/dist/components/chart-missing-plugin.js +4 -4
  89. package/dist/components/chart-missing-plugin.js.map +1 -1
  90. package/dist/utils/math-markdown.cjs +1 -1
  91. package/dist/utils/math-markdown.cjs.map +1 -1
  92. package/dist/utils/math-markdown.js +1 -1
  93. package/dist/utils/math-markdown.js.map +1 -1
  94. package/dist/utils/theme.cjs +91 -0
  95. package/dist/utils/theme.cjs.map +1 -0
  96. package/dist/utils/theme.d.ts +11 -0
  97. package/dist/utils/theme.d.ts.map +1 -0
  98. package/dist/utils/theme.js +91 -0
  99. package/dist/utils/theme.js.map +1 -0
  100. package/docs/SECURITY-DEPENDENCIES.md +81 -0
  101. package/docs/THEMING.md +205 -0
  102. package/package.json +5 -3
  103. package/src/components/ActionGroupRenderer.tsx +6 -6
  104. package/src/components/AutocompleteDropdown.tsx +17 -17
  105. package/src/components/AutocompleteFormField.tsx +10 -9
  106. package/src/components/ChartJSRenderer.tsx +483 -239
  107. package/src/components/ChatPrompt.tsx +16 -16
  108. package/src/components/ComponentToolbar.tsx +5 -5
  109. package/src/components/DegradedFallback.tsx +9 -9
  110. package/src/components/ExpandableWrapper.tsx +15 -11
  111. package/src/components/FormFieldRenderer.tsx +52 -51
  112. package/src/components/FormRenderer.tsx +12 -12
  113. package/src/components/GenerativeUIErrorBoundary.tsx +6 -6
  114. package/src/components/GhostText.tsx +7 -7
  115. package/src/components/GraphRenderer.tsx +83 -67
  116. package/src/components/MissingValue.tsx +2 -2
  117. package/src/components/ModalRenderer.tsx +11 -5
  118. package/src/components/PortalDropdownMenu.tsx +6 -2
  119. package/src/components/ScratchpadPanel.tsx +144 -141
  120. package/src/components/UIResourceRenderer.tsx +101 -101
  121. package/src/components/chart-missing-plugin.ts +5 -3
  122. package/src/styles/autocomplete.css +53 -53
  123. package/src/utils/math-markdown.ts +1 -1
  124. package/src/utils/theme.ts +101 -0
@@ -86,22 +86,22 @@ export const ChatPrompt: Component<ChatPromptProps> = (props) => {
86
86
 
87
87
  return (
88
88
  <div
89
- class="w-full max-w-2xl mx-auto mb-2 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg overflow-visible"
89
+ class="mcp-ui-root w-full max-w-2xl mx-auto mb-2 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-xl border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] shadow-[var(--mcp-ui-shadow,var(--shadow-lg))] overflow-visible"
90
90
  style={{ animation: 'chat-prompt-slide-up 0.2s ease-out' }}
91
91
  role="dialog"
92
92
  aria-label={props.config.title}
93
93
  >
94
94
  {/* Header */}
95
- <div class="flex items-center justify-between px-4 py-2.5 border-b border-gray-100 dark:border-gray-700">
96
- <p class="text-sm font-medium text-gray-900 dark:text-white">{props.config.title}</p>
95
+ <div class="flex items-center justify-between px-4 py-2.5 border-b border-[color:var(--mcp-ui-border,var(--color-gray-100,#f3f4f6))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]">
96
+ <p class="text-sm font-medium text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]">{props.config.title}</p>
97
97
  <button
98
98
  onClick={() => {
99
99
  props.onDismiss?.()
100
100
  props.onSubmit({ type: props.config.type, value: '', label: '', dismissed: true })
101
101
  }}
102
102
  class={props.dismissLabel
103
- ? 'px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors'
104
- : 'p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors'
103
+ ? 'px-3 py-1 text-xs font-medium text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-50))] dark:hover:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_20%,transparent))] rounded-lg transition-colors'
104
+ : 'p-1 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] rounded-full hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-colors'
105
105
  }
106
106
  aria-label={props.dismissLabel || strings.dismiss}
107
107
  >
@@ -172,7 +172,7 @@ const ChoiceBody: Component<{
172
172
  }
173
173
 
174
174
  const buttonClass = () => {
175
- const base = 'px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-blue-50 hover:border-blue-300 dark:hover:bg-blue-900/30 dark:hover:border-blue-600 transition-colors text-left'
175
+ const base = 'px-4 py-2 text-sm font-medium rounded-lg border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-50))] hover:border-[color:var(--mcp-ui-accent-border,var(--color-blue-300))] dark:hover:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_30%,transparent))] dark:hover:border-[color:var(--mcp-ui-accent-border,var(--color-blue-600))] transition-colors text-left'
176
176
  const extra = props.config.buttonClass
177
177
  return extra ? `${base} ${extra}` : base
178
178
  }
@@ -195,7 +195,7 @@ const ChoiceBody: Component<{
195
195
  </Show>
196
196
  {option.label}
197
197
  <Show when={option.description}>
198
- <span class="block text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-normal">{option.description}</span>
198
+ <span class="block text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] mt-0.5 font-normal">{option.description}</span>
199
199
  </Show>
200
200
  </>
201
201
  }
@@ -222,21 +222,21 @@ const ConfirmBody: Component<{
222
222
  return (
223
223
  <div>
224
224
  <Show when={props.config.message}>
225
- <p class="text-sm text-gray-600 dark:text-gray-400 mb-3">{props.config.message}</p>
225
+ <p class="text-sm text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] mb-3">{props.config.message}</p>
226
226
  </Show>
227
227
  <div class="flex gap-2 justify-end">
228
228
  <button
229
229
  onClick={props.onCancel}
230
- class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
230
+ class="px-4 py-2 text-sm font-medium rounded-lg border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-colors"
231
231
  >
232
232
  {props.config.cancelLabel || strings.cancel}
233
233
  </button>
234
234
  <button
235
235
  onClick={props.onConfirm}
236
- class={`px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors ${
236
+ class={`px-4 py-2 text-sm font-medium rounded-lg text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] transition-colors ${
237
237
  isDanger()
238
- ? 'bg-red-600 hover:bg-red-700'
239
- : 'bg-blue-600 hover:bg-blue-700'
238
+ ? 'bg-[color:var(--mcp-ui-danger,var(--color-red-600))] hover:bg-[color:var(--mcp-ui-danger-hover,var(--color-red-700))]'
239
+ : 'bg-[color:var(--mcp-ui-accent,var(--color-blue-600))] hover:bg-[color:var(--mcp-ui-accent-hover,var(--color-blue-700))]'
240
240
  }`}
241
241
  >
242
242
  {props.config.confirmLabel || strings.confirm}
@@ -380,11 +380,11 @@ const FormBody: Component<{
380
380
 
381
381
  {/* Live preview */}
382
382
  <Show when={previewText() || previewLoading()}>
383
- <div class="px-3 py-2 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800 text-sm">
383
+ <div class="px-3 py-2 bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-50))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_20%,transparent))] rounded-lg border border-[color:var(--mcp-ui-accent-border,var(--color-blue-200))] dark:border-[color:var(--mcp-ui-accent-border,var(--color-blue-800))] text-sm">
384
384
  <Show when={previewLoading()} fallback={
385
- <p class="text-blue-700 dark:text-blue-300">{previewText()}</p>
385
+ <p class="text-[color:var(--mcp-ui-accent,var(--color-blue-700))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-300))]">{previewText()}</p>
386
386
  }>
387
- <p class="text-blue-400 animate-pulse">{strings.promptLoadingPreview}</p>
387
+ <p class="text-[color:var(--mcp-ui-accent,var(--color-blue-400))] animate-pulse">{strings.promptLoadingPreview}</p>
388
388
  </Show>
389
389
  </div>
390
390
  </Show>
@@ -393,7 +393,7 @@ const FormBody: Component<{
393
393
  <button
394
394
  type="submit"
395
395
  disabled={!isValid()}
396
- class="px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
396
+ class="px-4 py-2 text-sm font-medium rounded-lg text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] bg-[color:var(--mcp-ui-accent,var(--color-blue-600))] hover:bg-[color:var(--mcp-ui-accent-hover,var(--color-blue-700))] disabled:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-400,#9ca3af))] disabled:cursor-not-allowed transition-colors"
397
397
  >
398
398
  {props.config.submitLabel || strings.submit}
399
399
  </button>
@@ -58,7 +58,7 @@ export const ComponentToolbar: Component<ComponentToolbarProps> = (props) => {
58
58
  }
59
59
 
60
60
  return (
61
- <div class={`absolute ${positionClasses()} z-10 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity`}>
61
+ <div class={`mcp-ui-root absolute ${positionClasses()} z-10 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity`}>
62
62
  <For each={props.actions}>
63
63
  {(action) => <ToolbarButton action={action} />}
64
64
  </For>
@@ -81,15 +81,15 @@ const ToolbarButton: Component<{ action: ToolbarAction }> = (props) => {
81
81
  const iconPath = () => showCheck() ? ICONS.check : ICONS[props.action.icon]
82
82
  const isActive = () => props.action.active
83
83
  const colorClass = () => {
84
- if (showCheck()) return 'text-green-500'
85
- if (isActive()) return 'text-blue-500 dark:text-blue-400'
86
- return 'text-gray-500 dark:text-gray-400'
84
+ if (showCheck()) return 'text-[color:var(--mcp-ui-success,var(--color-green-500))]'
85
+ if (isActive()) return 'text-[color:var(--mcp-ui-accent,var(--color-blue-500))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))]'
86
+ return 'text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]'
87
87
  }
88
88
 
89
89
  return (
90
90
  <button
91
91
  onClick={handleClick}
92
- class={`p-1.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm`}
92
+ class={`p-1.5 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded-full hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-all shadow-[var(--mcp-ui-shadow,var(--shadow-sm))]`}
93
93
  title={props.action.label}
94
94
  aria-label={props.action.label}
95
95
  data-mcp-ui-action={props.action.icon}
@@ -64,22 +64,22 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
64
64
 
65
65
  return (
66
66
  <div
67
- class="w-full rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20"
67
+ class="w-full rounded-lg border border-[color:var(--mcp-ui-warning-border,var(--color-amber-200))] bg-[color:var(--mcp-ui-warning-soft,var(--color-amber-50))] p-3 dark:border-[color:var(--mcp-ui-warning-border,var(--color-amber-800))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-amber-900)_20%,transparent))]"
68
68
  role="alert"
69
69
  >
70
- <p class="text-sm font-medium text-amber-900 dark:text-amber-100">{props.message}</p>
71
- <p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300">
70
+ <p class="text-sm font-medium text-[color:var(--mcp-ui-warning,var(--color-amber-900))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-100))]">{props.message}</p>
71
+ <p class="mt-0.5 text-xs text-[color:var(--mcp-ui-warning,var(--color-amber-700))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-300))]">
72
72
  {props.caption ?? strings.degradedCaption}
73
73
  </p>
74
74
 
75
75
  <Show when={hasTable()}>
76
- <div class="mt-2 max-h-64 overflow-auto rounded border border-amber-200 dark:border-amber-800">
76
+ <div class="mt-2 max-h-64 overflow-auto rounded border border-[color:var(--mcp-ui-warning-border,var(--color-amber-200))] dark:border-[color:var(--mcp-ui-warning-border,var(--color-amber-800))]">
77
77
  <table class="w-full border-collapse text-left text-xs">
78
- <thead class="sticky top-0 bg-amber-100 dark:bg-amber-900/40">
78
+ <thead class="sticky top-0 bg-[color:var(--mcp-ui-warning-soft,var(--color-amber-100))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-amber-900)_40%,transparent))]">
79
79
  <tr>
80
80
  <For each={props.columns}>
81
81
  {(col) => (
82
- <th class="px-2 py-1 font-medium text-amber-900 dark:text-amber-100">{col}</th>
82
+ <th class="px-2 py-1 font-medium text-[color:var(--mcp-ui-warning,var(--color-amber-900))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-100))]">{col}</th>
83
83
  )}
84
84
  </For>
85
85
  </tr>
@@ -87,10 +87,10 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
87
87
  <tbody>
88
88
  <For each={shownRows()}>
89
89
  {(row, rowIndex) => (
90
- <tr class="border-t border-amber-100 dark:border-amber-800/60">
90
+ <tr class="border-t border-[color:var(--mcp-ui-warning-border,var(--color-amber-100))] dark:border-[color:var(--mcp-ui-warning-border,color-mix(in_oklab,var(--color-amber-800)_60%,transparent))]">
91
91
  <For each={props.columns}>
92
92
  {(_col, i) => (
93
- <td class="px-2 py-1 text-amber-800 dark:text-amber-200">
93
+ <td class="px-2 py-1 text-[color:var(--mcp-ui-warning,var(--color-amber-800))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-200))]">
94
94
  <Show when={isMissing(rowIndex(), i())} fallback={String(row[i()] ?? '')}>
95
95
  <MissingValueMark />
96
96
  </Show>
@@ -107,7 +107,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
107
107
  <MissingValueLegend class="mt-2" />
108
108
  </Show>
109
109
  <Show when={hiddenCount() > 0}>
110
- <p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">
110
+ <p class="mt-1 text-[10px] text-[color:var(--mcp-ui-warning,var(--color-amber-600))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-400))]">
111
111
  {formatMCPUIString(strings.degradedMoreRows, { count: hiddenCount() })}
112
112
  </p>
113
113
  </Show>
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { Component, Show, createSignal, createEffect, onCleanup, JSX, createContext, useContext, Accessor } from 'solid-js'
10
10
  import { Portal } from 'solid-js/web'
11
+ import { bindPortalTheme } from '../utils/theme'
11
12
  import { useMCPUIStrings } from '../context/MCPUIStringsContext'
12
13
 
13
14
  /** Context for child components to know if they're in expanded/fullscreen view */
@@ -154,7 +155,7 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
154
155
  }
155
156
 
156
157
  return (
157
- <div class="relative group">
158
+ <div class="mcp-ui-root relative group">
158
159
  {/* Inline slot — content lives here when not expanded */}
159
160
  <div ref={inlineSlotRef}>
160
161
  <div ref={contentRef}>
@@ -172,12 +173,12 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
172
173
  props.toolbarVariant === 'always-visible'
173
174
  ? 'opacity-60 hover:opacity-100'
174
175
  : 'opacity-0 group-hover:opacity-70 hover:!opacity-100'
175
- } p-1.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm`}
176
+ } p-1.5 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded-full hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-all shadow-[var(--mcp-ui-shadow,var(--shadow-sm))]`}
176
177
  title={strings.expand}
177
178
  aria-label={strings.expand}
178
179
  data-mcp-ui-action="expand"
179
180
  >
180
- <svg class="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
181
+ <svg class="w-3.5 h-3.5 text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
181
182
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" />
182
183
  </svg>
183
184
  </button>
@@ -186,7 +187,7 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
186
187
  <Show when={isExpanded()}>
187
188
  <Portal>
188
189
  <div
189
- class="fixed inset-0 z-50 flex flex-col bg-black/50 backdrop-blur-sm"
190
+ class="fixed inset-0 z-50 flex flex-col bg-[color:var(--mcp-ui-overlay,color-mix(in_oklab,var(--color-black,#000)_50%,transparent))] backdrop-blur-sm"
190
191
  style={{ animation: 'expandable-fade-in 0.15s ease-out' }}
191
192
  onClick={handleBackdropClick}
192
193
  onPointerDown={handleBackdropPointerDown}
@@ -195,17 +196,20 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
195
196
  aria-modal="true"
196
197
  aria-label={props.title || strings.expandedView}
197
198
  tabIndex={-1}
198
- ref={dialogRef}
199
+ ref={(element) => {
200
+ dialogRef = element
201
+ bindPortalTheme(element, () => inlineSlotRef)
202
+ }}
199
203
  >
200
204
  {/* Modal panel */}
201
205
  <div
202
- class="relative flex flex-col m-4 flex-1 bg-white dark:bg-gray-800 rounded-lg shadow-xl overflow-hidden"
206
+ class="relative flex flex-col m-4 flex-1 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-xl))] overflow-hidden"
203
207
  style={{ animation: 'expandable-scale-in 0.15s ease-out' }}
204
208
  onClick={(e) => e.stopPropagation()}
205
209
  >
206
210
  {/* Header */}
207
- <div class="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
208
- <h2 class="text-lg font-semibold text-gray-900 dark:text-white truncate">
211
+ <div class="flex items-center justify-between px-4 py-3 border-b border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] flex-shrink-0">
212
+ <h2 class="text-lg font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] truncate">
209
213
  {props.title || strings.expandedView}
210
214
  </h2>
211
215
  <div class="flex items-center gap-2">
@@ -214,7 +218,7 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
214
218
  <button
215
219
  type="button"
216
220
  onClick={handleCopy}
217
- class="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
221
+ class="p-1.5 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] rounded-full hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-colors"
218
222
  title={props.copyLabel || strings.copyToClipboard}
219
223
  aria-label={props.copyLabel || strings.copyToClipboard}
220
224
  data-mcp-ui-action="copy"
@@ -222,7 +226,7 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
222
226
  <Show
223
227
  when={!copied()}
224
228
  fallback={
225
- <svg class="w-5 h-5 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
229
+ <svg class="w-5 h-5 text-[color:var(--mcp-ui-success,var(--color-green-500))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
226
230
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
227
231
  </svg>
228
232
  }
@@ -237,7 +241,7 @@ export const ExpandableWrapper: Component<ExpandableWrapperProps> = (props) => {
237
241
  <button
238
242
  type="button"
239
243
  onClick={handleClose}
240
- class="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
244
+ class="p-1.5 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] rounded-full hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] transition-colors"
241
245
  aria-label={strings.closeExpandedView}
242
246
  data-mcp-ui-action="close"
243
247
  >
@@ -64,14 +64,15 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
64
64
 
65
65
  const baseInputClass = () => `
66
66
  w-full px-3 py-2 border rounded-md
67
- focus:ring-2 focus:ring-blue-500 focus:border-blue-500
68
- disabled:bg-gray-100 disabled:cursor-not-allowed
67
+ ${!isMuted() ? 'text-[color:var(--mcp-ui-fg,inherit)]' : ''}
68
+ focus:ring-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] focus:border-[color:var(--mcp-ui-focus,var(--color-blue-500))]
69
+ disabled:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] disabled:cursor-not-allowed
69
70
  ${props.error
70
- ? 'border-red-500 focus:ring-red-500'
71
- : 'border-gray-300 dark:border-gray-600'}
72
- dark:bg-gray-700 dark:text-white
71
+ ? 'border-[color:var(--mcp-ui-danger-border,var(--color-red-500))] focus:ring-[color:var(--mcp-ui-focus,var(--color-red-500))]'
72
+ : 'border-[color:var(--mcp-ui-border-control,var(--color-gray-300,#d1d5db))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))]'}
73
+ dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]
73
74
  ${isUnsupported() ? 'opacity-50' : ''}
74
- ${isMuted() ? 'bg-gray-50 dark:bg-gray-700/50 text-gray-500 dark:text-gray-400' : ''}
75
+ ${isMuted() ? 'bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface-muted,color-mix(in_oklab,var(--color-gray-700,#374151)_50%,transparent))] text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]' : ''}
75
76
  `
76
77
 
77
78
  const fieldId = () => `field-${props.field.name}`
@@ -83,20 +84,20 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
83
84
  <Show when={props.field.label && props.field.type !== 'checkbox'}>
84
85
  <label
85
86
  for={fieldId()}
86
- class={`block text-sm font-medium ${isUnsupported() ? 'text-gray-400 dark:text-gray-500' : isMuted() ? 'text-gray-400 dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}`}
87
+ class={`block text-sm font-medium ${isUnsupported() ? 'text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] dark:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))]' : isMuted() ? 'text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] dark:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))]' : 'text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]'}`}
87
88
  >
88
89
  <Show when={sourceBadge()}>
89
90
  <span class="mr-1" title={strings[sourceBadge()!.titleKey]}>{sourceBadge()!.icon}</span>
90
91
  </Show>
91
92
  {props.field.label}
92
93
  <Show when={props.field.required || status() === 'required'}>
93
- <span class="text-red-500 ml-1" aria-hidden="true">*</span>
94
+ <span class="text-[color:var(--mcp-ui-danger,var(--color-red-500))] ml-1" aria-hidden="true">*</span>
94
95
  </Show>
95
96
  <Show when={isUnsupported()}>
96
- <span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">{strings.fieldNotSupported}</span>
97
+ <span class="ml-2 text-[10px] font-medium bg-[color:var(--mcp-ui-warning-soft,var(--color-orange-100))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-orange-900)_30%,transparent))] text-[color:var(--mcp-ui-warning,var(--color-orange-600))] dark:text-[color:var(--mcp-ui-warning,var(--color-orange-400))] px-1.5 py-0.5 rounded">{strings.fieldNotSupported}</span>
97
98
  </Show>
98
99
  <Show when={status() === 'unknown'}>
99
- <span class="ml-2 text-[10px] font-medium bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded">?</span>
100
+ <span class="ml-2 text-[10px] font-medium bg-[color:var(--mcp-ui-warning-soft,var(--color-yellow-100))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-yellow-900)_30%,transparent))] text-[color:var(--mcp-ui-warning,var(--color-yellow-600))] dark:text-[color:var(--mcp-ui-warning,var(--color-yellow-400))] px-1.5 py-0.5 rounded">?</span>
100
101
  </Show>
101
102
  </label>
102
103
  </Show>
@@ -217,12 +218,12 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
217
218
  disabled={props.disabled}
218
219
  aria-invalid={!!props.error}
219
220
  aria-describedby={props.error ? errorId() : undefined}
220
- class="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
221
+ class="w-4 h-4 rounded border-[color:var(--mcp-ui-border-control,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-accent,var(--color-blue-600))] focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))]"
221
222
  />
222
- <span class="text-sm text-gray-700 dark:text-gray-300">
223
+ <span class="text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]">
223
224
  {props.field.checkboxLabel || props.field.label}
224
225
  <Show when={props.field.required}>
225
- <span class="text-red-500 ml-1" aria-hidden="true">*</span>
226
+ <span class="text-[color:var(--mcp-ui-danger,var(--color-red-500))] ml-1" aria-hidden="true">*</span>
226
227
  </Show>
227
228
  </span>
228
229
  </label>
@@ -270,9 +271,9 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
270
271
  checked={props.value === option.value}
271
272
  onChange={() => props.onChange(option.value)}
272
273
  disabled={props.disabled || option.disabled}
273
- class="w-4 h-4 border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600"
274
+ class="w-4 h-4 border-[color:var(--mcp-ui-border-control,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-accent,var(--color-blue-600))] focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))]"
274
275
  />
275
- <span class="text-sm text-gray-700 dark:text-gray-300">
276
+ <span class="text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]">
276
277
  {option.label}
277
278
  </span>
278
279
  </label>
@@ -294,9 +295,9 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
294
295
  max={props.field.max}
295
296
  step={props.field.step || 1}
296
297
  disabled={isFieldDisabled()}
297
- class="flex-1 h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer accent-blue-600"
298
+ class="flex-1 h-2 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-200,#e5e7eb))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-600,#4b5563))] rounded-lg appearance-none cursor-pointer accent-[color:var(--mcp-ui-accent,var(--color-blue-600))]"
298
299
  />
299
- <span class="text-sm font-mono text-gray-700 dark:text-gray-300 min-w-[3rem] text-right">{props.value ?? props.field.min ?? 0}</span>
300
+ <span class="text-sm font-mono text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] min-w-[3rem] text-right">{props.value ?? props.field.min ?? 0}</span>
300
301
  </div>
301
302
  </Match>
302
303
 
@@ -314,19 +315,19 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
314
315
  aria-checked={!!props.value}
315
316
  onClick={() => props.onChange(!props.value)}
316
317
  disabled={isFieldDisabled()}
317
- class={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${props.value ? 'bg-blue-600' : 'bg-gray-300 dark:bg-gray-600'} ${isFieldDisabled() ? 'opacity-50 cursor-not-allowed' : ''}`}
318
+ class={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${props.value ? 'bg-[color:var(--mcp-ui-accent,var(--color-blue-600))]' : 'bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-300,#d1d5db))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-600,#4b5563))]'} ${isFieldDisabled() ? 'opacity-50 cursor-not-allowed' : ''}`}
318
319
  >
319
- <span class={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${props.value ? 'translate-x-4' : 'translate-x-0.5'}`} />
320
+ <span class={`inline-block h-3.5 w-3.5 transform rounded-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] transition-transform ${props.value ? 'translate-x-4' : 'translate-x-0.5'}`} />
320
321
  </button>
321
- <span class="text-sm text-gray-700 dark:text-gray-300">{props.field.checkboxLabel || props.field.label}</span>
322
+ <span class="text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]">{props.field.checkboxLabel || props.field.label}</span>
322
323
  </label>
323
324
  </Match>
324
325
 
325
326
  {/* Fieldset/Group */}
326
327
  <Match when={props.field.type === 'fieldset'}>
327
- <fieldset class="border border-gray-200 dark:border-gray-600 rounded-lg p-3">
328
- <legend class="text-xs font-medium text-gray-500 dark:text-gray-400 px-1">{props.field.label}</legend>
329
- <p class="text-xs text-gray-400">{props.field.helpText || strings.fieldGroupContainer}</p>
328
+ <fieldset class="border border-[color:var(--mcp-ui-border-control,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))] rounded-lg p-3">
329
+ <legend class="text-xs font-medium text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] px-1">{props.field.label}</legend>
330
+ <p class="text-xs text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">{props.field.helpText || strings.fieldGroupContainer}</p>
330
331
  </fieldset>
331
332
  </Match>
332
333
 
@@ -342,7 +343,7 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
342
343
  disabled={props.disabled}
343
344
  class={baseInputClass()}
344
345
  />
345
- <p class="text-xs text-amber-500 mt-0.5">
346
+ <p class="text-xs text-[color:var(--mcp-ui-warning,var(--color-amber-500))] mt-0.5">
346
347
  {formatMCPUIString(strings.fieldUnknownType, { type: props.field.type })}
347
348
  </p>
348
349
  </Match>
@@ -350,27 +351,27 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
350
351
 
351
352
  <Show when={props.field.statusReason}>
352
353
  <p class={`text-xs ${
353
- isUnsupported() ? 'text-orange-500 dark:text-orange-400'
354
- : status() === 'unknown' ? 'text-yellow-500 dark:text-yellow-400'
355
- : status() === 'required' ? 'text-blue-500 dark:text-blue-400'
356
- : 'text-gray-500 dark:text-gray-400'
354
+ isUnsupported() ? 'text-[color:var(--mcp-ui-warning,var(--color-orange-500))] dark:text-[color:var(--mcp-ui-warning,var(--color-orange-400))]'
355
+ : status() === 'unknown' ? 'text-[color:var(--mcp-ui-warning,var(--color-yellow-500))] dark:text-[color:var(--mcp-ui-warning,var(--color-yellow-400))]'
356
+ : status() === 'required' ? 'text-[color:var(--mcp-ui-accent,var(--color-blue-500))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))]'
357
+ : 'text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]'
357
358
  }`}>
358
359
  {props.field.statusReason}
359
360
  </p>
360
361
  </Show>
361
362
 
362
363
  <Show when={props.field.displayHint && props.field.prefill != null}>
363
- <p class={`text-xs italic ${isMuted() ? 'text-gray-400 dark:text-gray-500' : 'text-gray-500 dark:text-gray-400'}`}>
364
+ <p class={`text-xs italic ${isMuted() ? 'text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] dark:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))]' : 'text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]'}`}>
364
365
  {props.field.displayHint}
365
366
  </p>
366
367
  </Show>
367
368
 
368
369
  <Show when={props.field.helpText && !props.error && !props.field.statusReason && !props.field.displayHint}>
369
- <p class="text-xs text-gray-500 dark:text-gray-400">{props.field.helpText}</p>
370
+ <p class="text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">{props.field.helpText}</p>
370
371
  </Show>
371
372
 
372
373
  <Show when={props.error}>
373
- <p id={errorId()} role="alert" class="text-xs text-red-600 dark:text-red-400">
374
+ <p id={errorId()} role="alert" class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))]">
374
375
  {props.error}
375
376
  </p>
376
377
  </Show>
@@ -423,12 +424,12 @@ const MultiSelectField: Component<{
423
424
  <div class="flex flex-wrap gap-1 mb-1">
424
425
  <For each={props.value}>
425
426
  {(val) => (
426
- <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
427
+ <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-100))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_30%,transparent))] text-[color:var(--mcp-ui-accent,var(--color-blue-700))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-300))] rounded-full">
427
428
  {getLabel(val)}
428
429
  <button
429
430
  type="button"
430
431
  onClick={() => removeChip(val)}
431
- class="hover:text-blue-900 dark:hover:text-blue-100"
432
+ class="hover:text-[color:var(--mcp-ui-accent,var(--color-blue-900))] dark:hover:text-[color:var(--mcp-ui-accent,var(--color-blue-100))]"
432
433
  aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
433
434
  >
434
435
  &times;
@@ -446,12 +447,12 @@ const MultiSelectField: Component<{
446
447
  disabled={props.disabled}
447
448
  class={`${props.baseClass} text-left flex items-center justify-between`}
448
449
  >
449
- <span class={props.value.length ? 'text-gray-900 dark:text-white' : 'text-gray-400'}>
450
+ <span class={props.value.length ? 'text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]' : 'text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]'}>
450
451
  {props.value.length
451
452
  ? formatMCPUIString(strings.fieldSelectedCount, { count: props.value.length })
452
453
  : props.field.placeholder || strings.fieldSelectPlaceholder}
453
454
  </span>
454
- <svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
455
+ <svg class="w-4 h-4 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
455
456
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
456
457
  </svg>
457
458
  </button>
@@ -459,18 +460,18 @@ const MultiSelectField: Component<{
459
460
  {/* Dropdown with filter */}
460
461
  <Show when={open()}>
461
462
  <div
462
- class="absolute z-50 mt-1 w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg"
463
+ class="absolute z-50 mt-1 w-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border-control,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))] rounded-md shadow-[var(--mcp-ui-shadow,var(--shadow-lg))]"
463
464
  style={{ "max-height": "320px", display: "flex", "flex-direction": "column" }}
464
465
  >
465
466
  {/* Search filter */}
466
467
  <Show when={(props.field.options?.length || 0) > 10}>
467
- <div class="p-2 border-b border-gray-200 dark:border-gray-600 flex-shrink-0">
468
+ <div class="p-2 border-b border-[color:var(--mcp-ui-border-control,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))] flex-shrink-0">
468
469
  <input
469
470
  type="text"
470
471
  value={filter()}
471
472
  onInput={(e) => setFilter(e.currentTarget.value)}
472
473
  placeholder={strings.filterPlaceholder}
473
- class="w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none"
474
+ class="w-full px-2 py-1 text-sm border border-[color:var(--mcp-ui-border-control,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))] rounded bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] focus:border-[color:var(--mcp-ui-focus,var(--color-blue-400))] outline-none"
474
475
  autofocus
475
476
  />
476
477
  </div>
@@ -479,19 +480,19 @@ const MultiSelectField: Component<{
479
480
  <div style={{ "overflow-y": "auto", "flex": "1", "-webkit-overflow-scrolling": "touch" }}>
480
481
  <For each={filteredOptions()}>
481
482
  {(option) => (
482
- <label class="flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer text-sm">
483
+ <label class="flex items-center gap-2 px-3 py-2 hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] cursor-pointer text-sm">
483
484
  <input
484
485
  type="checkbox"
485
486
  checked={(props.value || []).includes(option.value)}
486
487
  onChange={() => toggle(option.value)}
487
- class="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600"
488
+ class="w-4 h-4 rounded border-[color:var(--mcp-ui-border-control,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-accent,var(--color-blue-600))] focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))]"
488
489
  />
489
- <span class="text-gray-900 dark:text-white">{option.label}</span>
490
+ <span class="text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]">{option.label}</span>
490
491
  </label>
491
492
  )}
492
493
  </For>
493
494
  <Show when={filteredOptions().length === 0}>
494
- <p class="px-3 py-2 text-sm text-gray-400">{strings.fieldNoMatches}</p>
495
+ <p class="px-3 py-2 text-sm text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">{strings.fieldNoMatches}</p>
495
496
  </Show>
496
497
  </div>
497
498
  </div>
@@ -681,8 +682,8 @@ const AutocompleteField: Component<{
681
682
  <div class="relative">
682
683
  {/* Resolving indicator */}
683
684
  <Show when={resolving()}>
684
- <div class="flex items-center gap-1 mb-1 text-xs text-gray-400">
685
- <span class="animate-spin h-3 w-3 border border-gray-400 border-t-transparent rounded-full" />
685
+ <div class="flex items-center gap-1 mb-1 text-xs text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
686
+ <span class="animate-spin h-3 w-3 border border-[color:var(--mcp-ui-border-control,var(--color-gray-400,#9ca3af))] border-t-transparent rounded-full" />
686
687
  {strings.fieldResolving}
687
688
  </div>
688
689
  </Show>
@@ -692,12 +693,12 @@ const AutocompleteField: Component<{
692
693
  <div class="flex flex-wrap gap-1 mb-1">
693
694
  <For each={selectedValues()}>
694
695
  {(val) => (
695
- <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
696
+ <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-100))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_30%,transparent))] text-[color:var(--mcp-ui-accent,var(--color-blue-700))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-300))] rounded-full">
696
697
  {getLabel(val)}
697
698
  <button
698
699
  type="button"
699
700
  onClick={() => removeChip(val)}
700
- class="hover:text-blue-900 dark:hover:text-blue-100"
701
+ class="hover:text-[color:var(--mcp-ui-accent,var(--color-blue-900))] dark:hover:text-[color:var(--mcp-ui-accent,var(--color-blue-100))]"
701
702
  aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
702
703
  >
703
704
  &times;
@@ -723,7 +724,7 @@ const AutocompleteField: Component<{
723
724
  />
724
725
 
725
726
  <Show when={isOpen() && suggestions().length > 0}>
726
- <div class="absolute z-20 mt-1 w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg max-h-72 overflow-y-auto">
727
+ <div class="absolute z-20 mt-1 w-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border-control,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border-control,var(--color-gray-600,#4b5563))] rounded-md shadow-[var(--mcp-ui-shadow,var(--shadow-lg))] max-h-72 overflow-y-auto">
727
728
  <For each={suggestions()}>
728
729
  {(item) => {
729
730
  const isSelected = () => isMultiple() && selectedValues().includes(item.value)
@@ -732,8 +733,8 @@ const AutocompleteField: Component<{
732
733
  type="button"
733
734
  onMouseDown={(e) => e.preventDefault()}
734
735
  onClick={() => selectSuggestion(item)}
735
- class={`w-full text-left px-3 py-2 text-sm hover:bg-blue-50 dark:hover:bg-blue-900/20 ${
736
- isSelected() ? 'text-blue-600 dark:text-blue-400 bg-blue-50/50 dark:bg-blue-900/10' : 'text-gray-900 dark:text-white'
736
+ class={`w-full text-left px-3 py-2 text-sm hover:bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-50))] dark:hover:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_20%,transparent))] ${
737
+ isSelected() ? 'text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-50)_50%,transparent))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_10%,transparent))]' : 'text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]'
737
738
  }`}
738
739
  disabled={isSelected()}
739
740
  >
@@ -791,9 +792,9 @@ const TagsField: Component<{
791
792
  <div class="flex flex-wrap gap-1 mb-1">
792
793
  <For each={props.value || []}>
793
794
  {(tag) => (
794
- <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
795
+ <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-100))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_30%,transparent))] text-[color:var(--mcp-ui-accent,var(--color-blue-700))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-300))] rounded-full">
795
796
  {tag}
796
- <button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={formatMCPUIString(strings.removeItem, { name: tag })}>&times;</button>
797
+ <button type="button" onClick={() => removeTag(tag)} class="hover:text-[color:var(--mcp-ui-accent,var(--color-blue-900))] dark:hover:text-[color:var(--mcp-ui-accent,var(--color-blue-100))]" aria-label={formatMCPUIString(strings.removeItem, { name: tag })}>&times;</button>
797
798
  </span>
798
799
  )}
799
800
  </For>