stateurl
v0.4.6
Published
state in URL - Assign params. Navigate automatically. React router where URL params are reactive variables.
Maintainers
Readme
stateurl
state in URL — React router where URL params are assignable reactive variables
100% written by AI — This library is entirely developed by Claude (Anthropic) with human direction from Isaac.
✦ AI-First Design
StateURL is designed for AI-assisted programming. Every API decision optimizes for:
| Principle | Benefit | | ----------------------------- | ------------------------------------------ | | Simple, consistent API | Learn once, apply everywhere | | Minimal side effects | Changes are explicit and traceable | | Testable by design | Components are pure functions of URL state | | Extensible without complexity | Add features safely | | Clean separation of concerns | Work on focused, bounded problems | | API reveals intention | Code reads like plain English |
The same qualities that help AI understand your code help humans too — faster onboarding, fewer bugs, easier maintenance.
See DESIGN.md for the full philosophy and AI.md for common pitfalls.
✦ Assign to Navigate
param.productId = 42 // → /products/item/42
query.sort = 'price' // → ?sort=price
feature.theme = 'dark' // → /app/dark/...No navigate(). No callbacks. Just assign.
✦ Full Type Safety
Define once, autocomplete everywhere:
const config = {
path: 'user/:userId/post/:postId',
schema: {
param: { userId: 0, postId: 0 }, // number
query: { tab: 'info' }, // string (default: 'info')
},
trail: '/',
} as constYour editor knows everything:
function Post({ param, query }: SurlRouteProps<typeof config>) {
│
├─ param.userId → number
├─ param.postId → number
├─ query.tab → string
│
param.userId = 42 ✓ number assignment
param.userId = 'alice' ✗ Type 'string' is not assignable to 'number'
param.unknown ✗ Property 'unknown' does not exist
}✦ URL is Your State
No useState. No useEffect. No sync logic.
function ProductFilters({ query }: SurlRouteProps<typeof config>) {
useSignals()
return (
<select
value={query.sort}
onChange={(e) => (query.sort = e.target.value)}
>
<option value='name'>Name</option>
<option value='price'>Price</option>
</select>
)
}User selects "Price"
↓
query.sort = 'price'
↓
URL updates: /products?sort=price
↓
Shareable. Bookmarkable. Back button works.Live demo: stateurl.com ・ Docs: stateurl.com/docs
v0.4.0 — API may change before v1.0. Pin version if stability matters. Report issues
Install
npm install stateurl
# or
pnpm add stateurlQuick Start
// ProductDetail.tsx
const config = {
path: ':id',
schema: { param: { id: 0 } },
trail: '/products',
} as const
export const ProductDetailRoute = defineRoute(ProductDetail, config)
function ProductDetail({ param }: SurlRouteProps<typeof config>) {
return <div>Product {param.id}</div>
}// Products.tsx
const config = {
path: 'products',
trail: '/',
outlet: [ProductDetailRoute],
} as const
export const ProductsRoute = defineRoute(Products, config)// App.tsx
<Router
base='app'
feature={{ theme: ['light', 'dark'] }}
routes={[ProductsRoute]}
/>Navigate
go('/products/:id', { id: 42 }) // typed path with params
at.home.go() // first-level route
label.productDetail.go({ id: 42 }) // nested route by labelgo('/
│
├─ /home
├─ /products
├─ /products/:id ← autocomplete shows registered routes
├─ /users/:userId
└─ /settings// from component props
<button data-href={to(':id', { id: nextId })} onClick={handleHref}>
Next
</button>Read & Write
function User({ param }: SurlRouteProps<typeof config>) {
useSignals()
return <div>User {param.userId}</div> // read: just use it (number)
}
param.userId = 42 // write → URL updates
feature.theme = 'dark' // feature → /app/dark/...URL Structure
/app/dark/users/profile/123?sort=name#section
│ │ └─────────────┬─────────┘ │
│ │ resource path hash
│ └─ feature (theme)
└─ base| Layer | Example | Scope |
| --------- | --------------- | --------------- |
| feature | theme: 'dark' | App-wide |
| param | userId: 123 | Route-scoped |
| query | sort: 'name' | Component state |
| hash | #section | Scroll position |
Reference
Route Config
// No params in path → no schema needed
{ path: 'home', trail: '/' }
// Has :param in path → schema REQUIRED
{ path: 'user/:userId', trail: '/', schema: { param: { userId: 0 } } }
// Optional param :id? → schema still REQUIRED
// The ? is for route matching, not schema definition
{ path: 'user/:id?', trail: '/', schema: { param: { id: 0 } } }Why schema is required for params:
- Type safety in component (
param.id→ number) - Default values for navigation
- Runtime validation
Without schema: no types, no defaults, validation errors.
{
path: 'user/:userId',
trail: '/admin',
schema: {
param: { userId: 0 }, // required for :userId
query: { tab: '' }, // optional query params
},
label: 'adminUser',
outlet: [ChildRoute],
}Router Props
<Router base='app' feature={{ theme: ['light', 'dark'] }} routes={routes} />Component Props
function Component({
param,
query,
to,
breadcrumbs,
}: SurlRouteProps<typeof config>) {
useSignals()
// param.userId → number (from schema)
// query.sort → string (from schema)
// to('../') → relative navigation
// breadcrumbs → ['users', 'profile', '123']
}Route Guards
{ path: 'admin', case: [{ when: () => !auth, render: Login }], render: Admin }Transitions
const transition = useTransition() // null | { from, to }
{
transition && <LoadingBar />
}API
| Category | Export |
| ---------- | -------------------------------------------------------- |
| Components | Router Outlet ForkOutlet ErrorBoundary |
| Define | defineRoute(Component, config) defineRoutes(routes) |
| Navigate | go(path) replace(path) handleHref at.* label.* |
| State | param feature query path |
| Hooks | useSignals() useTransition() |
| Types | SurlRouteProps<typeof config> SurlRoute |
Roadmap
| Version | Features |
| ------- | ------------------------------------------------------------------------ |
| v0.4.0 | defineRoute(Component, config), optional schema, bottom-up composition |
| v0.5.0 | Trail validation, dev tools |
| v1.0.0 | Stable API, backward compatibility |
Credits
This library is 100% written by AI.
- Development: Claude (Anthropic)
- Direction & Vision: Isaac
Every line of code, every test, every documentation — generated by AI with human guidance. StateURL demonstrates that AI can build production-quality libraries when given the right design principles.
MIT License • 2025
