diff --git a/.changeset/green-bags-admire.md b/.changeset/green-bags-admire.md
new file mode 100644
index 00000000..7726310c
--- /dev/null
+++ b/.changeset/green-bags-admire.md
@@ -0,0 +1,6 @@
+---
+'@tanstack/devtools': minor
+'@tanstack/devtools-ui': patch
+---
+
+extracted common UI components into a separate package
diff --git a/packages/devtools-ui/README.md b/packages/devtools-ui/README.md
new file mode 100644
index 00000000..2966a73a
--- /dev/null
+++ b/packages/devtools-ui/README.md
@@ -0,0 +1,5 @@
+# @tanstack/devtools-ui
+
+Set of Solid.js UI components for TanStack Devtools.
+
+These are used across the TanStack ecosystem to provide a consistent and customizable user interface for your devtools.
diff --git a/packages/devtools-ui/eslint.config.js b/packages/devtools-ui/eslint.config.js
new file mode 100644
index 00000000..e472c69e
--- /dev/null
+++ b/packages/devtools-ui/eslint.config.js
@@ -0,0 +1,10 @@
+// @ts-check
+
+import rootConfig from '../../eslint.config.js'
+
+export default [
+ ...rootConfig,
+ {
+ rules: {},
+ },
+]
diff --git a/packages/devtools-ui/package.json b/packages/devtools-ui/package.json
new file mode 100644
index 00000000..685f741e
--- /dev/null
+++ b/packages/devtools-ui/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "@tanstack/devtools-ui",
+ "version": "0.2.1",
+ "description": "TanStack Devtools UI is a set of UI components for building devtool panels for your application.",
+ "author": "Tanner Linsley",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/TanStack/devtools.git",
+ "directory": "packages/devtools"
+ },
+ "homepage": "https://tanstack.com/devtools",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "keywords": [
+ "devtools"
+ ],
+ "type": "module",
+ "types": "dist/esm/index.d.ts",
+ "main": "dist/cjs/index.cjs",
+ "module": "dist/esm/index.js",
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/esm/index.d.ts",
+ "default": "./dist/esm/index.js"
+ },
+ "require": {
+ "types": "./dist/cjs/index.d.cts",
+ "default": "./dist/cjs/index.cjs"
+ }
+ },
+ "./package.json": "./package.json"
+ },
+ "sideEffects": false,
+ "engines": {
+ "node": ">=18"
+ },
+ "files": [
+ "dist/",
+ "src"
+ ],
+ "scripts": {
+ "clean": "premove ./build ./dist",
+ "lint:fix": "eslint ./src --fix",
+ "test:eslint": "eslint ./src",
+ "test:lib": "vitest",
+ "test:lib:dev": "pnpm test:lib --watch",
+ "test:types": "tsc",
+ "test:build": "publint --strict",
+ "build": "vite build"
+ },
+ "dependencies": {
+ "goober": "^2.1.16",
+ "solid-js": "^1.9.7"
+ },
+ "peerDependencies": {
+ "solid-js": ">=1.9.7"
+ },
+ "devDependencies": {
+ "vite-plugin-solid": "^2.11.6"
+ }
+}
diff --git a/packages/devtools/src/components/checkbox.tsx b/packages/devtools-ui/src/components/checkbox.tsx
similarity index 100%
rename from packages/devtools/src/components/checkbox.tsx
rename to packages/devtools-ui/src/components/checkbox.tsx
diff --git a/packages/devtools/src/components/input.tsx b/packages/devtools-ui/src/components/input.tsx
similarity index 100%
rename from packages/devtools/src/components/input.tsx
rename to packages/devtools-ui/src/components/input.tsx
diff --git a/packages/devtools/src/components/logo.tsx b/packages/devtools-ui/src/components/logo.tsx
similarity index 100%
rename from packages/devtools/src/components/logo.tsx
rename to packages/devtools-ui/src/components/logo.tsx
diff --git a/packages/devtools/src/components/select.tsx b/packages/devtools-ui/src/components/select.tsx
similarity index 100%
rename from packages/devtools/src/components/select.tsx
rename to packages/devtools-ui/src/components/select.tsx
diff --git a/packages/devtools-ui/src/index.ts b/packages/devtools-ui/src/index.ts
new file mode 100644
index 00000000..dfaa2666
--- /dev/null
+++ b/packages/devtools-ui/src/index.ts
@@ -0,0 +1,4 @@
+export { Checkbox } from './components/checkbox'
+export { Input } from './components/input'
+export { Select } from './components/select'
+export { TanStackLogo } from './components/logo'
diff --git a/packages/devtools-ui/src/styles/tokens.ts b/packages/devtools-ui/src/styles/tokens.ts
new file mode 100644
index 00000000..9d247cf1
--- /dev/null
+++ b/packages/devtools-ui/src/styles/tokens.ts
@@ -0,0 +1,305 @@
+export const tokens = {
+ colors: {
+ inherit: 'inherit',
+ current: 'currentColor',
+ transparent: 'transparent',
+ black: '#000000',
+ white: '#ffffff',
+ neutral: {
+ 50: '#f9fafb',
+ 100: '#f2f4f7',
+ 200: '#eaecf0',
+ 300: '#d0d5dd',
+ 400: '#98a2b3',
+ 500: '#667085',
+ 600: '#475467',
+ 700: '#344054',
+ 800: '#1d2939',
+ 900: '#101828',
+ },
+ darkGray: {
+ 50: '#525c7a',
+ 100: '#49536e',
+ 200: '#414962',
+ 300: '#394056',
+ 400: '#313749',
+ 500: '#292e3d',
+ 600: '#212530',
+ 700: '#191c24',
+ 800: '#111318',
+ 900: '#0b0d10',
+ },
+ gray: {
+ 50: '#f9fafb',
+ 100: '#f2f4f7',
+ 200: '#eaecf0',
+ 300: '#d0d5dd',
+ 400: '#98a2b3',
+ 500: '#667085',
+ 600: '#475467',
+ 700: '#344054',
+ 800: '#1d2939',
+ 900: '#101828',
+ },
+ blue: {
+ 25: '#F5FAFF',
+ 50: '#EFF8FF',
+ 100: '#D1E9FF',
+ 200: '#B2DDFF',
+ 300: '#84CAFF',
+ 400: '#53B1FD',
+ 500: '#2E90FA',
+ 600: '#1570EF',
+ 700: '#175CD3',
+ 800: '#1849A9',
+ 900: '#194185',
+ },
+ green: {
+ 25: '#F6FEF9',
+ 50: '#ECFDF3',
+ 100: '#D1FADF',
+ 200: '#A6F4C5',
+ 300: '#6CE9A6',
+ 400: '#32D583',
+ 500: '#12B76A',
+ 600: '#039855',
+ 700: '#027A48',
+ 800: '#05603A',
+ 900: '#054F31',
+ },
+ red: {
+ 50: '#fef2f2',
+ 100: '#fee2e2',
+ 200: '#fecaca',
+ 300: '#fca5a5',
+ 400: '#f87171',
+ 500: '#ef4444',
+ 600: '#dc2626',
+ 700: '#b91c1c',
+ 800: '#991b1b',
+ 900: '#7f1d1d',
+ 950: '#450a0a',
+ },
+ yellow: {
+ 25: '#FFFCF5',
+ 50: '#FFFAEB',
+ 100: '#FEF0C7',
+ 200: '#FEDF89',
+ 300: '#FEC84B',
+ 400: '#FDB022',
+ 500: '#F79009',
+ 600: '#DC6803',
+ 700: '#B54708',
+ 800: '#93370D',
+ 900: '#7A2E0E',
+ },
+ purple: {
+ 25: '#FAFAFF',
+ 50: '#F4F3FF',
+ 100: '#EBE9FE',
+ 200: '#D9D6FE',
+ 300: '#BDB4FE',
+ 400: '#9B8AFB',
+ 500: '#7A5AF8',
+ 600: '#6938EF',
+ 700: '#5925DC',
+ 800: '#4A1FB8',
+ 900: '#3E1C96',
+ },
+ teal: {
+ 25: '#F6FEFC',
+ 50: '#F0FDF9',
+ 100: '#CCFBEF',
+ 200: '#99F6E0',
+ 300: '#5FE9D0',
+ 400: '#2ED3B7',
+ 500: '#15B79E',
+ 600: '#0E9384',
+ 700: '#107569',
+ 800: '#125D56',
+ 900: '#134E48',
+ },
+ pink: {
+ 25: '#fdf2f8',
+ 50: '#fce7f3',
+ 100: '#fbcfe8',
+ 200: '#f9a8d4',
+ 300: '#f472b6',
+ 400: '#ec4899',
+ 500: '#db2777',
+ 600: '#be185d',
+ 700: '#9d174d',
+ 800: '#831843',
+ 900: '#500724',
+ },
+ cyan: {
+ 25: '#ecfeff',
+ 50: '#cffafe',
+ 100: '#a5f3fc',
+ 200: '#67e8f9',
+ 300: '#22d3ee',
+ 400: '#06b6d4',
+ 500: '#0891b2',
+ 600: '#0e7490',
+ 700: '#155e75',
+ 800: '#164e63',
+ 900: '#083344',
+ },
+ },
+ alpha: {
+ 100: 'ff',
+ 90: 'e5',
+ 80: 'cc',
+ 70: 'b3',
+ 60: '99',
+ 50: '80',
+ 40: '66',
+ 30: '4d',
+ 20: '33',
+ 10: '1a',
+ 0: '00',
+ },
+ font: {
+ size: {
+ '2xs': 'calc(var(--tsrd-font-size) * 0.625)',
+ xs: 'calc(var(--tsrd-font-size) * 0.75)',
+ sm: 'calc(var(--tsrd-font-size) * 0.875)',
+ md: 'var(--tsrd-font-size)',
+ lg: 'calc(var(--tsrd-font-size) * 1.125)',
+ xl: 'calc(var(--tsrd-font-size) * 1.25)',
+ '2xl': 'calc(var(--tsrd-font-size) * 1.5)',
+ '3xl': 'calc(var(--tsrd-font-size) * 1.875)',
+ '4xl': 'calc(var(--tsrd-font-size) * 2.25)',
+ '5xl': 'calc(var(--tsrd-font-size) * 3)',
+ '6xl': 'calc(var(--tsrd-font-size) * 3.75)',
+ '7xl': 'calc(var(--tsrd-font-size) * 4.5)',
+ '8xl': 'calc(var(--tsrd-font-size) * 6)',
+ '9xl': 'calc(var(--tsrd-font-size) * 8)',
+ },
+ lineHeight: {
+ '3xs': 'calc(var(--tsrd-font-size) * 0.75)',
+ '2xs': 'calc(var(--tsrd-font-size) * 0.875)',
+ xs: 'calc(var(--tsrd-font-size) * 1)',
+ sm: 'calc(var(--tsrd-font-size) * 1.25)',
+ md: 'calc(var(--tsrd-font-size) * 1.5)',
+ lg: 'calc(var(--tsrd-font-size) * 1.75)',
+ xl: 'calc(var(--tsrd-font-size) * 2)',
+ '2xl': 'calc(var(--tsrd-font-size) * 2.25)',
+ '3xl': 'calc(var(--tsrd-font-size) * 2.5)',
+ '4xl': 'calc(var(--tsrd-font-size) * 2.75)',
+ '5xl': 'calc(var(--tsrd-font-size) * 3)',
+ '6xl': 'calc(var(--tsrd-font-size) * 3.25)',
+ '7xl': 'calc(var(--tsrd-font-size) * 3.5)',
+ '8xl': 'calc(var(--tsrd-font-size) * 3.75)',
+ '9xl': 'calc(var(--tsrd-font-size) * 4)',
+ },
+ weight: {
+ thin: '100',
+ extralight: '200',
+ light: '300',
+ normal: '400',
+ medium: '500',
+ semibold: '600',
+ bold: '700',
+ extrabold: '800',
+ black: '900',
+ },
+ fontFamily: {
+ sans: 'ui-sans-serif, Inter, system-ui, sans-serif, sans-serif',
+ mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace`,
+ },
+ },
+ breakpoints: {
+ xs: '320px',
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ border: {
+ radius: {
+ none: '0px',
+ xs: 'calc(var(--tsrd-font-size) * 0.125)',
+ sm: 'calc(var(--tsrd-font-size) * 0.25)',
+ md: 'calc(var(--tsrd-font-size) * 0.375)',
+ lg: 'calc(var(--tsrd-font-size) * 0.5)',
+ xl: 'calc(var(--tsrd-font-size) * 0.75)',
+ '2xl': 'calc(var(--tsrd-font-size) * 1)',
+ '3xl': 'calc(var(--tsrd-font-size) * 1.5)',
+ full: '9999px',
+ },
+ },
+ size: {
+ 0: '0px',
+ 0.25: 'calc(var(--tsrd-font-size) * 0.0625)',
+ 0.5: 'calc(var(--tsrd-font-size) * 0.125)',
+ 1: 'calc(var(--tsrd-font-size) * 0.25)',
+ 1.5: 'calc(var(--tsrd-font-size) * 0.375)',
+ 2: 'calc(var(--tsrd-font-size) * 0.5)',
+ 2.5: 'calc(var(--tsrd-font-size) * 0.625)',
+ 3: 'calc(var(--tsrd-font-size) * 0.75)',
+ 3.5: 'calc(var(--tsrd-font-size) * 0.875)',
+ 4: 'calc(var(--tsrd-font-size) * 1)',
+ 4.5: 'calc(var(--tsrd-font-size) * 1.125)',
+ 5: 'calc(var(--tsrd-font-size) * 1.25)',
+ 5.5: 'calc(var(--tsrd-font-size) * 1.375)',
+ 6: 'calc(var(--tsrd-font-size) * 1.5)',
+ 6.5: 'calc(var(--tsrd-font-size) * 1.625)',
+ 7: 'calc(var(--tsrd-font-size) * 1.75)',
+ 8: 'calc(var(--tsrd-font-size) * 2)',
+ 9: 'calc(var(--tsrd-font-size) * 2.25)',
+ 10: 'calc(var(--tsrd-font-size) * 2.5)',
+ 11: 'calc(var(--tsrd-font-size) * 2.75)',
+ 12: 'calc(var(--tsrd-font-size) * 3)',
+ 14: 'calc(var(--tsrd-font-size) * 3.5)',
+ 16: 'calc(var(--tsrd-font-size) * 4)',
+ 20: 'calc(var(--tsrd-font-size) * 5)',
+ 24: 'calc(var(--tsrd-font-size) * 6)',
+ 28: 'calc(var(--tsrd-font-size) * 7)',
+ 32: 'calc(var(--tsrd-font-size) * 8)',
+ 36: 'calc(var(--tsrd-font-size) * 9)',
+ 40: 'calc(var(--tsrd-font-size) * 10)',
+ 44: 'calc(var(--tsrd-font-size) * 11)',
+ 48: 'calc(var(--tsrd-font-size) * 12)',
+ 52: 'calc(var(--tsrd-font-size) * 13)',
+ 56: 'calc(var(--tsrd-font-size) * 14)',
+ 60: 'calc(var(--tsrd-font-size) * 15)',
+ 64: 'calc(var(--tsrd-font-size) * 16)',
+ 72: 'calc(var(--tsrd-font-size) * 18)',
+ 80: 'calc(var(--tsrd-font-size) * 20)',
+ 96: 'calc(var(--tsrd-font-size) * 24)',
+ },
+ shadow: {
+ xs: (_: string = 'rgb(0 0 0 / 0.1)') =>
+ `0 1px 2px 0 rgb(0 0 0 / 0.05)` as const,
+ sm: (color: string = 'rgb(0 0 0 / 0.1)') =>
+ `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}` as const,
+ md: (color: string = 'rgb(0 0 0 / 0.1)') =>
+ `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}` as const,
+ lg: (color: string = 'rgb(0 0 0 / 0.1)') =>
+ `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}` as const,
+ xl: (color: string = 'rgb(0 0 0 / 0.1)') =>
+ `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}` as const,
+ '2xl': (color: string = 'rgb(0 0 0 / 0.25)') =>
+ `0 25px 50px -12px ${color}` as const,
+ inner: (color: string = 'rgb(0 0 0 / 0.05)') =>
+ `inset 0 2px 4px 0 ${color}` as const,
+ none: () => `none` as const,
+ },
+ zIndices: {
+ hide: -1,
+ auto: 'auto',
+ base: 0,
+ docked: 10,
+ dropdown: 1000,
+ sticky: 1100,
+ banner: 1200,
+ overlay: 1300,
+ modal: 1400,
+ popover: 1500,
+ skipLink: 1600,
+ toast: 1700,
+ tooltip: 1800,
+ },
+} as const
diff --git a/packages/devtools-ui/src/styles/use-styles.ts b/packages/devtools-ui/src/styles/use-styles.ts
new file mode 100644
index 00000000..26926077
--- /dev/null
+++ b/packages/devtools-ui/src/styles/use-styles.ts
@@ -0,0 +1,195 @@
+import * as goober from 'goober'
+import { createSignal } from 'solid-js'
+import { tokens } from './tokens'
+
+const stylesFactory = () => {
+ const { colors, font, size, alpha } = tokens
+ const { fontFamily } = font
+ const css = goober.css
+
+ return {
+ logo: css`
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ background-color: transparent;
+ border: none;
+ width: ${size[12]};
+ height: ${size[12]};
+ font-family: ${fontFamily.sans};
+ gap: ${tokens.size[0.5]};
+ padding: 0px;
+ &:hover {
+ opacity: 0.7;
+ }
+ `,
+
+ selectWrapper: css`
+ width: 100%;
+ max-width: 300px;
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ `,
+ selectContainer: css`
+ width: 100%;
+ `,
+ selectLabel: css`
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: ${colors.gray[100]};
+ `,
+ selectDescription: css`
+ font-size: 0.8rem;
+ color: ${colors.gray[400]};
+ margin: 0;
+ line-height: 1.3;
+ `,
+ select: css`
+ appearance: none;
+ width: 100%;
+ padding: 0.75rem 3rem 0.75rem 0.75rem;
+ border-radius: 0.5rem;
+ background-color: ${colors.darkGray[800]};
+ color: ${colors.gray[100]};
+ border: 1px solid ${colors.gray[700]};
+ font-size: 0.875rem;
+ transition: all 0.2s ease;
+ cursor: pointer;
+
+ /* Custom arrow */
+ background-image: url("data:image/svg+xml;utf8,");
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 1.25rem;
+
+ &:hover {
+ border-color: ${colors.gray[600]};
+ }
+
+ &:focus {
+ outline: none;
+ border-color: ${colors.purple[400]};
+ box-shadow: 0 0 0 3px ${colors.purple[400]}${alpha[20]};
+ }
+ `,
+ inputWrapper: css`
+ width: 100%;
+ max-width: 300px;
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ `,
+ inputContainer: css`
+ width: 100%;
+ `,
+ inputLabel: css`
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: ${colors.gray[100]};
+ `,
+ inputDescription: css`
+ font-size: 0.8rem;
+ color: ${colors.gray[400]};
+ margin: 0;
+ line-height: 1.3;
+ `,
+ input: css`
+ appearance: none;
+ width: 100%;
+ padding: 0.75rem;
+ border-radius: 0.5rem;
+ background-color: ${colors.darkGray[800]};
+ color: ${colors.gray[100]};
+ border: 1px solid ${colors.gray[700]};
+ font-size: 0.875rem;
+ font-family: ${fontFamily.mono};
+ transition: all 0.2s ease;
+
+ &::placeholder {
+ color: ${colors.gray[500]};
+ }
+
+ &:hover {
+ border-color: ${colors.gray[600]};
+ }
+
+ &:focus {
+ outline: none;
+ border-color: ${colors.purple[400]};
+ box-shadow: 0 0 0 3px ${colors.purple[400]}${alpha[20]};
+ }
+ `,
+ checkboxWrapper: css`
+ display: flex;
+ align-items: flex-start;
+ gap: 0.75rem;
+ cursor: pointer;
+ user-select: none;
+ padding: 0.5rem;
+ border-radius: 0.5rem;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: ${colors.darkGray[800]};
+ }
+ `,
+ checkboxContainer: css`
+ width: 100%;
+ `,
+ checkboxLabelContainer: css`
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ flex: 1;
+ `,
+ checkbox: css`
+ appearance: none;
+ width: 1.25rem;
+ height: 1.25rem;
+ border: 2px solid ${colors.gray[700]};
+ border-radius: 0.375rem;
+ background-color: ${colors.darkGray[800]};
+ display: grid;
+ place-items: center;
+ transition: all 0.2s ease;
+ flex-shrink: 0;
+ margin-top: 0.125rem;
+
+ &:hover {
+ border-color: ${colors.purple[400]};
+ }
+
+ &:checked {
+ background-color: ${colors.purple[500]};
+ border-color: ${colors.purple[500]};
+ }
+
+ &:checked::after {
+ content: '';
+ width: 0.4rem;
+ height: 0.6rem;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+ margin-top: -3px;
+ }
+ `,
+ checkboxLabel: css`
+ color: ${colors.gray[100]};
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.4;
+ `,
+ checkboxDescription: css`
+ color: ${colors.gray[400]};
+ font-size: 0.8rem;
+ line-height: 1.3;
+ `,
+ }
+}
+
+export function useStyles() {
+ const [_styles] = createSignal(stylesFactory())
+ return _styles
+}
diff --git a/packages/devtools-ui/tests/index.test.ts b/packages/devtools-ui/tests/index.test.ts
new file mode 100644
index 00000000..9ca0bdfc
--- /dev/null
+++ b/packages/devtools-ui/tests/index.test.ts
@@ -0,0 +1,7 @@
+import { describe, expect, it } from 'vitest'
+
+describe('devtools', () => {
+ it('should pass', () => {
+ expect(true).toBe(true)
+ })
+})
diff --git a/packages/devtools-ui/tests/test-setup.ts b/packages/devtools-ui/tests/test-setup.ts
new file mode 100644
index 00000000..a9d0dd31
--- /dev/null
+++ b/packages/devtools-ui/tests/test-setup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom/vitest'
diff --git a/packages/devtools-ui/tsconfig.docs.json b/packages/devtools-ui/tsconfig.docs.json
new file mode 100644
index 00000000..2880b4df
--- /dev/null
+++ b/packages/devtools-ui/tsconfig.docs.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["tests", "src"]
+}
diff --git a/packages/devtools-ui/tsconfig.json b/packages/devtools-ui/tsconfig.json
new file mode 100644
index 00000000..50837fb4
--- /dev/null
+++ b/packages/devtools-ui/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": [
+ "src",
+ "eslint.config.js",
+ "vite.config.ts",
+ "tests",
+ "src/server"
+ ],
+ "compilerOptions": {
+ "jsxImportSource": "solid-js",
+ "jsx": "preserve"
+ }
+}
diff --git a/packages/devtools-ui/vite.config.ts b/packages/devtools-ui/vite.config.ts
new file mode 100644
index 00000000..d20bd8ab
--- /dev/null
+++ b/packages/devtools-ui/vite.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig, mergeConfig } from 'vitest/config'
+import { tanstackViteConfig } from '@tanstack/config/vite'
+import solid from 'vite-plugin-solid'
+import packageJson from './package.json'
+import type { Plugin } from 'vite'
+
+const config = defineConfig({
+ plugins: [solid() as any satisfies Plugin],
+ test: {
+ name: packageJson.name,
+ dir: './',
+ watch: false,
+ environment: 'jsdom',
+ setupFiles: ['./tests/test-setup.ts'],
+ globals: true,
+ },
+})
+
+export default mergeConfig(
+ config,
+ tanstackViteConfig({
+ entry: ['./src/index.ts'],
+ srcDir: './src',
+ }),
+)
diff --git a/packages/devtools/package.json b/packages/devtools/package.json
index 1b2545fe..f6e9e1f6 100644
--- a/packages/devtools/package.json
+++ b/packages/devtools/package.json
@@ -55,6 +55,7 @@
"dependencies": {
"@solid-primitives/keyboard": "^1.2.8",
"@tanstack/devtools-event-bus": "workspace:*",
+ "@tanstack/devtools-ui": "workspace:*",
"clsx": "^2.1.1",
"goober": "^2.1.16",
"solid-js": "^1.9.7"
diff --git a/packages/devtools/src/components/trigger.tsx b/packages/devtools/src/components/trigger.tsx
index 789880f9..35043d94 100644
--- a/packages/devtools/src/components/trigger.tsx
+++ b/packages/devtools/src/components/trigger.tsx
@@ -1,8 +1,8 @@
import { createMemo } from 'solid-js'
import clsx from 'clsx'
+import { TanStackLogo } from '@tanstack/devtools-ui'
import { useDevtoolsSettings } from '../context/use-devtools-context'
import { useStyles } from '../styles/use-styles'
-import { TanStackLogo } from './logo'
import type { Accessor } from 'solid-js'
export const Trigger = ({
diff --git a/packages/devtools/src/styles/use-styles.ts b/packages/devtools/src/styles/use-styles.ts
index 103ff66b..ea5b10a9 100644
--- a/packages/devtools/src/styles/use-styles.ts
+++ b/packages/devtools/src/styles/use-styles.ts
@@ -55,22 +55,6 @@ const stylesFactory = () => {
transform: translateY(${height}px);
`
},
- logo: css`
- cursor: pointer;
- display: flex;
- flex-direction: column;
- background-color: transparent;
- border: none;
- width: ${size[12]};
- height: ${size[12]};
- font-family: ${fontFamily.sans};
- gap: ${tokens.size[0.5]};
- padding: 0px;
- &:hover {
- opacity: 0.7;
- }
- `,
-
devtoolsPanel: css`
display: flex;
font-size: ${fontSize.sm};
@@ -269,168 +253,7 @@ const stylesFactory = () => {
height: 100%;
overflow-y: auto;
`,
- selectWrapper: css`
- width: 100%;
- max-width: 300px;
- display: flex;
- flex-direction: column;
- gap: 0.375rem;
- `,
- selectContainer: css`
- width: 100%;
- `,
- selectLabel: css`
- font-size: 0.875rem;
- font-weight: 500;
- color: ${colors.gray[100]};
- `,
- selectDescription: css`
- font-size: 0.8rem;
- color: ${colors.gray[400]};
- margin: 0;
- line-height: 1.3;
- `,
- select: css`
- appearance: none;
- width: 100%;
- padding: 0.75rem 3rem 0.75rem 0.75rem;
- border-radius: 0.5rem;
- background-color: ${colors.darkGray[800]};
- color: ${colors.gray[100]};
- border: 1px solid ${colors.gray[700]};
- font-size: 0.875rem;
- transition: all 0.2s ease;
- cursor: pointer;
- /* Custom arrow */
- background-image: url("data:image/svg+xml;utf8,");
- background-repeat: no-repeat;
- background-position: right 0.75rem center;
- background-size: 1.25rem;
-
- &:hover {
- border-color: ${colors.gray[600]};
- }
-
- &:focus {
- outline: none;
- border-color: ${colors.purple[400]};
- box-shadow: 0 0 0 3px ${colors.purple[400]}${alpha[20]};
- }
- `,
- inputWrapper: css`
- width: 100%;
- max-width: 300px;
- display: flex;
- flex-direction: column;
- gap: 0.375rem;
- `,
- inputContainer: css`
- width: 100%;
- `,
- inputLabel: css`
- font-size: 0.875rem;
- font-weight: 500;
- color: ${colors.gray[100]};
- `,
- inputDescription: css`
- font-size: 0.8rem;
- color: ${colors.gray[400]};
- margin: 0;
- line-height: 1.3;
- `,
- input: css`
- appearance: none;
- width: 100%;
- padding: 0.75rem;
- border-radius: 0.5rem;
- background-color: ${colors.darkGray[800]};
- color: ${colors.gray[100]};
- border: 1px solid ${colors.gray[700]};
- font-size: 0.875rem;
- font-family: ${fontFamily.mono};
- transition: all 0.2s ease;
-
- &::placeholder {
- color: ${colors.gray[500]};
- }
-
- &:hover {
- border-color: ${colors.gray[600]};
- }
-
- &:focus {
- outline: none;
- border-color: ${colors.purple[400]};
- box-shadow: 0 0 0 3px ${colors.purple[400]}${alpha[20]};
- }
- `,
- checkboxWrapper: css`
- display: flex;
- align-items: flex-start;
- gap: 0.75rem;
- cursor: pointer;
- user-select: none;
- padding: 0.5rem;
- border-radius: 0.5rem;
- transition: background-color 0.2s ease;
-
- &:hover {
- background-color: ${colors.darkGray[800]};
- }
- `,
- checkboxContainer: css`
- width: 100%;
- `,
- checkboxLabelContainer: css`
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
- flex: 1;
- `,
- checkbox: css`
- appearance: none;
- width: 1.25rem;
- height: 1.25rem;
- border: 2px solid ${colors.gray[700]};
- border-radius: 0.375rem;
- background-color: ${colors.darkGray[800]};
- display: grid;
- place-items: center;
- transition: all 0.2s ease;
- flex-shrink: 0;
- margin-top: 0.125rem;
-
- &:hover {
- border-color: ${colors.purple[400]};
- }
-
- &:checked {
- background-color: ${colors.purple[500]};
- border-color: ${colors.purple[500]};
- }
-
- &:checked::after {
- content: '';
- width: 0.4rem;
- height: 0.6rem;
- border: solid white;
- border-width: 0 2px 2px 0;
- transform: rotate(45deg);
- margin-top: -3px;
- }
- `,
- checkboxLabel: css`
- color: ${colors.gray[100]};
- font-size: 0.875rem;
- font-weight: 500;
- line-height: 1.4;
- `,
- checkboxDescription: css`
- color: ${colors.gray[400]};
- font-size: 0.8rem;
- line-height: 1.3;
- `,
settingsContainer: css`
padding: 1.5rem;
height: 100%;
diff --git a/packages/devtools/src/tabs/settings-tab.tsx b/packages/devtools/src/tabs/settings-tab.tsx
index 746835f3..791f448c 100644
--- a/packages/devtools/src/tabs/settings-tab.tsx
+++ b/packages/devtools/src/tabs/settings-tab.tsx
@@ -1,9 +1,7 @@
import { Show } from 'solid-js'
-import { Input } from '../components/input'
-import { Select } from '../components/select'
+import { Checkbox, Input, Select } from '@tanstack/devtools-ui'
import { useDevtoolsSettings } from '../context/use-devtools-context'
import { uppercaseFirstLetter } from '../utils/sanitize'
-import { Checkbox } from '../components/checkbox'
import { useStyles } from '../styles/use-styles'
export const SettingsTab = () => {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b97cb882..bf281ad7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -240,6 +240,8 @@ importers:
specifier: ^4.2.4
version: 4.2.4
+ examples/react/start/generated/prisma: {}
+
examples/solid/basic:
dependencies:
'@tanstack/solid-devtools':
@@ -276,6 +278,9 @@ importers:
'@tanstack/devtools-event-bus':
specifier: workspace:*
version: link:../event-bus
+ '@tanstack/devtools-ui':
+ specifier: workspace:*
+ version: link:../devtools-ui
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -290,6 +295,19 @@ importers:
specifier: ^2.11.6
version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.6(@types/node@22.15.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
+ packages/devtools-ui:
+ dependencies:
+ goober:
+ specifier: ^2.1.16
+ version: 2.1.16(csstype@3.1.3)
+ solid-js:
+ specifier: ^1.9.7
+ version: 1.9.7
+ devDependencies:
+ vite-plugin-solid:
+ specifier: ^2.11.6
+ version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.6(@types/node@22.15.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
+
packages/event-bus:
dependencies:
ws: