token-efficient ui library ecosystem for llms
~75% fewer output tokens than react | ~10kb minified | 0 deps
comprehensive benchmarks comparing tooey vs react are available in packages/ui/benchmarks/BENCHMARK_RESULTS.md.
tokens counted using gpt-4 tokenizer. lower is better for llm cost and context.
| component | tooey | react | savings |
|---|---|---|---|
| counter | 51 | 102 | 50% |
| todo list | 87 | 194 | 55% |
| form | 131 | 240 | 45% |
| tabs | 96 | 116 | 17% |
| modal | 127 | 178 | 29% |
| data table | 83 | 120 | 31% |
| shopping cart | 140 | 282 | 50% |
| wizard | 216 | 356 | 39% |
| total | 931 | 1588 | 41% |
benchmarks run in jsdom environment.
| benchmark | result |
|---|---|
| render 1000 items | ~20ms |
| 10,000 state updates | ~69ms (6.9μs/update) |
| signal with 100 subscribers | ~10ms for 10k updates |
| library | minified | gzipped |
|---|---|---|
| tooey | ~10 KB | ~4 KB |
| react + reactdom | ~140 KB | ~45 KB |
| preact | ~10 KB | ~4 KB |
| vue 3 | ~40 KB | ~16 KB |
run benchmarks locally:
cd packages/ui
pnpm benchmark # full benchmark with report
pnpm benchmark:test # performance tests via vitest| package | description | version |
|---|---|---|
| @tooey/ui | core library | |
| @tooey/components | component library | coming soon |
| @tooey/claude-plugin | claude code plugin |
npm install @tooey/uiimport { render, vs, hs, tx, bt } from '@tooey/ui';
render(document.getElementById('app'), {
s: { n: 0 },
r: [vs, [[tx, { $: 'n' }], [hs, [[bt, '-', { c: 'n-' }], [bt, '+', { c: 'n+' }]], { g: 8 }]], { g: 8 }]
});generate token-efficient ui specs directly in claude code.
# add the tooey marketplace
claude plugin marketplace add https://raw.githubusercontent.com/vijaypemmaraju/tooey/main/marketplace.json
# install the plugin
claude plugin install tooey/tooey:ui a counter with increment and decrement buttons
/tooey:ui a login form with username and password fields
/tooey:ui a modal dialog with close button
see @tooey/claude-plugin for more installation options.
tooey is designed to be extensible while preserving token efficiency.
- functions over registration - custom components are just functions, no registry
- composition over configuration - build complex from simple primitives
- short names stay short - extensions follow abbreviation patterns
- tree-shakeable - everything importable individually
- zero overhead - extensions don't bloat base library
- llm-friendly - patterns that ai can learn and generate efficiently
no registration needed. a component is just a function that returns a NodeSpec.
type Component<P = {}> = (props?: P, children?: NodeSpec[]) => NodeSpec;// define a Card component
const Card = (props = {}, children = []) => [
vs,
children,
{ bg: '#fff', p: 16, r: 8, sh: '0 2px 4px rgba(0,0,0,0.1)', ...props }
];
// usage in spec (token-efficient)
{ r: [Card, [[tx, 'Hello']], { bg: '#f0f0f0' }] }
// or with helper
{ r: Card({ bg: '#f0f0f0' }, [[tx, 'Hello']]) }// components can define their own state keys
const Counter = (props = {}) => ({
// state contribution (merged with parent spec.s)
s: { count: props.initial || 0 },
// component tree
r: [hs, [
[bt, '-', { c: 'count-' }],
[tx, { $: 'count' }],
[bt, '+', { c: 'count+' }]
], { g: 8, ...props }]
});
// usage - returns TooeySpec, can be spread or rendered directly
render(el, Counter({ initial: 5 }))third-party packages export components following naming conventions.
| full name | abbrev | rule |
|---|---|---|
| Card | Cd |
first + last consonant |
| Modal | Mdl |
consonant cluster |
| Tabs | Tbs |
plural → add 's' |
| Alert | Al |
first 2 letters |
| Dropdown | Dd |
first letter + key consonant |
| Tooltip | Tt |
double first letter |
import { Cd, Mdl, Al } from '@tooey/components';
{ r: [Cd, [[tx, 'Content']], { variant: 'outlined' }] }// define theme
const theme = {
card: { bg: '#fff', r: 8, sh: '0 2px 8px rgba(0,0,0,0.1)' },
btn: { bg: '#007bff', fg: '#fff', r: 4, p: '8 16' },
input: { bw: 1, bc: '#ccc', r: 4, p: 8 }
};
// apply via spread
[vs, [...], { ...theme.card, p: 24 }]// new api
const { render, theme } = createTooey({
colors: { primary: '#007bff', danger: '#dc3545' },
spacing: { sm: 8, md: 16, lg: 24 },
radius: { sm: 4, md: 8, lg: 16 }
});
// usage with $ prefix for theme values
[bt, 'Save', { bg: '$primary', p: '$md', r: '$sm' }]lightweight hooks for cross-cutting concerns.
interface TooeyPlugin {
name: string;
// lifecycle hooks
onInit?(instance: TooeyInstance): void;
onDestroy?(instance: TooeyInstance): void;
// render hooks
beforeRender?(spec: NodeSpec, ctx: RenderContext): NodeSpec;
afterRender?(el: HTMLElement, spec: NodeSpec): void;
// state hooks
onStateChange?(key: string, oldVal: unknown, newVal: unknown): void;
// extend instance
extend?: Record<string, Function>;
}// router plugin
const routerPlugin = {
name: 'router',
onInit(instance) {
instance.state.route = signal(location.pathname);
window.addEventListener('popstate', () => {
instance.set('route', location.pathname);
});
},
extend: {
navigate(path) {
history.pushState({}, '', path);
this.set('route', path);
}
}
};
// logger plugin
const loggerPlugin = {
name: 'logger',
onStateChange(key, oldVal, newVal) {
console.log(`[${key}]`, oldVal, '→', newVal);
}
};
// usage
const app = render(el, spec, { plugins: [routerPlugin, loggerPlugin] });
app.navigate('/about');for complex component composition.
// component with slots
const Layout = (props, children) => {
const { header, footer, ...rest } = props;
return [vs, [
header && [dv, [header], { cls: 'header' }],
[dv, children, { cls: 'main', s: { flex: 1 } }],
footer && [dv, [footer], { cls: 'footer' }]
], { h: '100vh', ...rest }];
};
// usage
[Layout, [
[tx, 'Main content']
], {
header: [hs, [[tx, 'Logo'], [tx, 'Nav']], { jc: 'sb' }],
footer: [tx, '© 2024']
}]reusable state patterns.
// define a state module
const formState = (fields) => ({
s: {
values: Object.fromEntries(fields.map(f => [f, ''])),
errors: {},
touched: {},
submitting: false
},
actions: {
setValue: (field, value) => ['values', '.', [field, value]],
setError: (field, error) => ['errors', '.', [field, error]],
submit: () => ['submitting', '!', true]
}
});
// usage
const form = formState(['email', 'password']);
render(el, {
s: { ...form.s, otherState: 123 },
r: [vs, [
[In, '', { v: { $: 'values.email' }, x: form.actions.setValue.bind(null, 'email') }],
[bt, 'Submit', { c: form.actions.submit }]
]]
});derived values that update automatically.
import { signal, computed, render } from '@tooey/ui';
const items = signal([{ price: 10 }, { price: 20 }]);
const total = computed(() => items().reduce((sum, i) => sum + i.price, 0));
// total() automatically updates when items changeshandle loading states elegantly.
// async$ helper
function async$(promise, { loading, error }) {
return {
s: { data: null, loading: true, error: null },
init: async (instance) => {
try {
const data = await promise;
instance.set('data', data);
} catch (e) {
instance.set('error', e.message);
} finally {
instance.set('loading', false);
}
}
};
}
// usage
const userSpec = async$(fetch('/api/user').then(r => r.json()), {
loading: [tx, 'Loading...'],
error: [tx, { $: 'error' }, { fg: 'red' }]
});
render(el, {
s: userSpec.s,
r: { '?': 'loading',
t: userSpec.loading,
e: { '?': 'error', t: userSpec.error, e: [tx, { $: 'data.name' }] }
}
});typescript support for custom components.
interface CardProps extends Props {
variant?: 'filled' | 'outlined' | 'elevated';
clickable?: boolean;
}
const Card: Component<CardProps> = (props = {}, children = []) => {
const { variant = 'elevated', clickable, ...rest } = props;
const styles = {
filled: { bg: '#f5f5f5' },
outlined: { bw: 1, bc: '#ddd' },
elevated: { sh: '0 2px 8px rgba(0,0,0,0.1)' }
};
return [vs, children, {
...styles[variant],
r: 8,
p: 16,
cur: clickable ? 'pointer' : undefined,
...rest
}];
};| pattern | tokens | notes |
|---|---|---|
[Card, [...], {}] |
~5 | same as built-in |
Card({}, [...]) |
~6 | slightly more |
{ ...theme.card } |
~4 | very efficient |
{ bg: '$primary' } |
~5 | same as hardcoded |
[Cd, [...]] |
~3 | abbreviated |
all patterns maintain token efficiency comparable to core components.
this is a pnpm monorepo.
# install dependencies
pnpm install
# build all packages
pnpm build
# run tests
pnpm test
# run in watch mode
pnpm devmit