state in URL - Assign params. Navigate automatically. React router where URL params are reactive variables.
npm install 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.
---
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.
---
``typescript`
param.productId = 42 // → /products/item/42
query.sort = 'price' // → ?sort=price
feature.theme = 'dark' // → /app/dark/...
No navigate(). No callbacks. Just assign.
---
Define once, autocomplete everywhere:
`typescript`
const config = {
path: 'user/:userId/post/:postId',
schema: {
param: { userId: 0, postId: 0 }, // number
query: { tab: 'info' }, // string (default: 'info')
},
trail: '/',
} as const
Your editor knows everything:
``
function Post({ param, query }: SurlRouteProps
│
├─ 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
}
---
No useState. No useEffect. No sync logic.
`typescript
function ProductFilters({ query }: SurlRouteProps
useSignals()
return (
value={query.sort}
onChange={(e) => (query.sort = e.target.value)}
>
)
}
`
``
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
---
`bash`
npm install stateurlor
pnpm add stateurl
---
`typescript
// ProductDetail.tsx
const config = {
path: ':id',
schema: { param: { id: 0 } },
trail: '/products',
} as const
export const ProductDetailRoute = defineRoute(ProductDetail, config)
function ProductDetail({ param }: SurlRouteProps
return Product {param.id}
}
`
`typescript
// Products.tsx
const config = {
path: 'products',
trail: '/',
outlet: [ProductDetailRoute],
} as const
export const ProductsRoute = defineRoute(Products, config)
`
`tsx`
// App.tsx
feature={{ theme: ['light', 'dark'] }}
routes={[ProductsRoute]}
/>
`typescript`
go('/products/:id', { id: 42 }) // typed path with params
at.home.go() // first-level route
label.productDetail.go({ id: 42 }) // nested route by label
``
go('/
│
├─ /home
├─ /products
├─ /products/:id ← autocomplete shows registered routes
├─ /users/:userId
└─ /settings
`tsx`
// from component props
`typescript
function User({ param }: SurlRouteProps
useSignals()
return
param.userId = 42 // write → URL updates
feature.theme = 'dark' // feature → /app/dark/...
`
---
``
/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 |
---
`typescript
// 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.
`typescript`
{
path: 'user/:userId',
trail: '/admin',
schema: {
param: { userId: 0 }, // required for :userId
query: { tab: '' }, // optional query params
},
label: 'adminUser',
outlet: [ChildRoute],
}
`tsx`
`typescript`
function Component({
param,
query,
to,
breadcrumbs,
}: SurlRouteProps
useSignals()
// param.userId → number (from schema)
// query.sort → string (from schema)
// to('../') → relative navigation
// breadcrumbs → ['users', 'profile', '123']
}
`typescript`
{ path: 'admin', case: [{ when: () => !auth, render: Login }], render: Admin }
`typescript`
const transition = useTransition() // null | { from, to }
{
transition &&
}
---
| Category | Export |
| ---------- | -------------------------------------------------------- |
| Components | Router Outlet ForkOutlet ErrorBoundary |defineRoute(Component, config)
| Define | defineRoutes(routes) |go(path)
| Navigate | replace(path) handleHref at. label. |param
| State | feature query path |useSignals()
| Hooks | useTransition() |SurlRouteProps
| Types | SurlRoute |
---
| 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 |
---
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