Documentation
Everything you need to build with Speck.js, the AI-native web framework.
Quick Start
Installation
$ npm create speck-app my-app
$ cd my-app
$ npm run dev
Your First Component
Create src/components/Hello.speck:
<Hello>
<state name={"World"} />
<div>
<h1>Hello, {state.name.value}!</h1>
<input
value={state.name.value}
onInput={(e) => state.name.value = e.target.value}
/>
</div>
</Hello>
Use it anywhere—no imports needed:
<App>
<Hello />
</App>
Project Structure
my-speck-app/
├── src/
│ ├── components/ # Your .speck components
│ │ ├── App.speck
│ │ ├── Header.speck
│ │ └── Counter.speck
│ ├── .compiled/ # Auto-generated (don't edit)
│ │ ├── App.jsx
│ │ └── _componentRegistry.js
│ └── lib/
│ └── agent-runtime.js # AI agent runtime
├── api/
│ ├── server.js # API server
│ └── db.js # Memory database
├── compiler/
│ └── compiler.js # Speck compiler
├── .env # API keys (git-ignored)
├── package.json
└── vite.config.js
CLI Commands
| Command | Description |
|---|---|
npm run dev |
Start dev server + compiler + AI proxy |
npm run dev:no-agent |
Start without AI proxy |
npm run build |
Production build |
npm run compile |
One-time compile all components |
npm run watch |
Watch for .speck changes |
Components
Components are .speck files that combine markup, state, and
logic in a clean syntax. The component name is the filename.
<MyComponent>
<!-- State declarations -->
<state count={0} />
<!-- Script for functions -->
<script>
const increment = () => state.count.value++;
</script>
<!-- Your markup -->
<div>
<p>Count: {state.count.value}</p>
<button onClick={increment}>+</button>
</div>
</MyComponent>
State Management
Declare reactive state with <state name={value} />.
Access and mutate via state.name.value.
<Counter>
<state count={0} />
<state name={"Guest"} />
<p>Hello, {state.name.value}! Count: {state.count.value}</p>
<button onClick={() => state.count.value++}>+</button>
</Counter>
Script Blocks
Use <script> for component logic, functions, and computed values:
<Calculator>
<state a={0} />
<state b={0} />
<script>
const sum = () => state.a.value + state.b.value;
const multiply = () => state.a.value * state.b.value;
</script>
<p>Sum: {sum()}, Product: {multiply()}</p>
</Calculator>
Conditionals (if)
Use <if condition={expr}> for conditional rendering:
<Toggle>
<state show={false} />
<button onClick={() => state.show.value = !state.show.value}>
Toggle
</button>
<if condition={state.show.value}>
<p>Now you see me!</p>
</if>
</Toggle>
Loops (loop)
Use <loop of={array} let={item}> to iterate:
<UserList>
<state users={["Alice", "Bob", "Charlie"]} />
<ul>
<loop of={state.users.value} let={user}>
<li>{user}</li>
</loop>
</ul>
</UserList>
Switch/Case
Use <switch on={value}> with <case when="x">:
<StatusBadge>
<props status />
<switch on={props.status}>
<case when="success">
<span class="green">✓ Success</span>
</case>
<case when="error">
<span class="red">✗ Error</span>
</case>
<default>
<span>Unknown</span>
</default>
</switch>
</StatusBadge>
Routing
Built-in client-side routing with <Router> and <route>:
<App>
<Router>
<route path="/">
<HomePage />
</route>
<route path="/user/:id" let={params}>
<UserProfile userId={params.id} />
</route>
</Router>
</App>
Async Data
Handle promises declaratively with <async>:
<DataLoader>
<async promise={fetch('/api/data').then(r => r.json())}>
<loading>
<p>Loading...</p>
</loading>
<then let={data}>
<p>Got: {data.name}</p>
</then>
<catch let={error}>
<p>Error: {error.message}</p>
</catch>
</async>
</DataLoader>
Props
Declare accepted props with <props name1 name2 />:
<Button>
<props label onClick variant />
<button
class={props.variant || "primary"}
onClick={props.onClick}
>
{props.label}
</button>
</Button>
<!-- Usage -->
<Button label="Click me" onClick={handleClick} />
Slots
Use <slot /> for content projection:
<Card>
<div class="card">
<div class="card-header">
<slot name="header" />
</div>
<div class="card-body">
<slot />
</div>
</div>
</Card>
<!-- Usage -->
<Card>
<template slot="header">My Title</template>
<p>Card content goes here</p>
</Card>
Lifecycle
Use <onMount> for side effects on mount:
<Dashboard>
<state data={null} />
<onMount>
{fetch('/api/dashboard').then(r => r.json()).then(d => state.data.value = d)}
</onMount>
<if condition={state.data.value}>
<DashboardContent data={state.data.value} />
</if>
</Dashboard>
AI with Agent Components
Speck.js is AI-native with built-in Agent components. Add AI chat to any component with a single line:
One-Liner Chat UI
<SimpleChat>
<Agent.Chat purpose="You are a helpful assistant" />
</SimpleChat>
That's it. Full chat interface with input, submit button, streaming responses, error handling, and chat history.
Agent.Chat Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | "default-agent" | Unique identifier for memory isolation |
purpose |
string | "You are a helpful AI assistant." | System prompt |
model |
string | "claude-sonnet-4-20250514" | Claude model |
temperature |
number | 0.7 | Response randomness (0-1) |
maxTokens |
number | 1000 | Max response length |
memory |
boolean | false | Enable persistent memory |
memoryDepth |
number | 100 | Number of interactions to remember |
placeholder |
string | "Type a message..." | Input placeholder |
submitLabel |
string | "Send" | Button text |
showHistory |
boolean | true | Show chat history |
Agent Memory NEW in v0.4.1
Speck.js agents can remember conversations across browser sessions with a single prop. No configuration, no external services—just persistent memory that works.
Enable Memory
<SmartBot>
<Agent.Chat
id="my-assistant"
purpose="You are a helpful assistant"
memory={true}
/>
</SmartBot>
How It Works
- Session ID: Generated on first visit, stored in localStorage, reused forever
- Interactions: Every message saved to local SQLite database
- Context Loading: On each message, recent interactions loaded into agent context
- Persistence: Memory survives page refreshes, browser restarts, and computer reboots
Memory Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | "default-agent" | Required for memory. Unique identifier that isolates this agent's memories. |
memory |
boolean | false | Enable persistent memory across sessions |
memoryDepth |
number | 100 | Number of past interactions to load into context |
Multiple Agents with Separate Memories
<App>
<!-- Each agent has isolated memory -->
<Agent.Chat
id="support-bot"
purpose="Customer support agent"
memory={true}
/>
<Agent.Chat
id="coding-assistant"
purpose="Help with programming"
memory={true}
/>
</App>
Customize Memory Depth
<!-- Remember last 50 interactions -->
<Agent.Chat
id="my-bot"
memory={true}
memoryDepth={50}
/>
id prop is required when using memory.
Without it, all agents would share the same memory pool. Give each agent a unique ID
to keep their conversations separate.
Agent.Ask
For single prompt/response tasks like translation, summarization, or extraction:
Basic Usage
<Translator>
<state input={""} />
<state output={""} />
<input
value={state.input.value}
onInput={(e) => state.input.value = e.target.value}
/>
<Agent.Ask
purpose="Translate to French. Output only the translation."
autoSend={false}
onResponse={(text) => state.output.value = text}
>
{({ send, loading }) => (
<button onClick={() => send(state.input.value)}>
{loading ? "Translating..." : "Translate"}
</button>
)}
</Agent.Ask>
<p>{state.output.value}</p>
</Translator>
Agent.Ask Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | "default-agent" | Unique identifier for memory |
purpose |
string | Required | System prompt |
prompt |
string | - | Message to send |
autoSend |
boolean | true | Send on mount |
memory |
boolean | false | Enable persistent memory |
memoryDepth |
number | 5 | Interactions to remember |
onResponse |
function | - | Called with response |
onError |
function | - | Called on error |
Compound Components
For full customization, use the
<Agent> provider with sub-components:
<CustomChat>
<Agent
id="code-reviewer"
purpose="You are a code reviewer"
temperature={0.3}
memory={true}
>
<Agent.History />
<Agent.Error />
<Agent.Response />
<Agent.Input placeholder="Paste code..." />
<Agent.Submit>Review</Agent.Submit>
<Agent.Clear>Reset</Agent.Clear>
</Agent>
</CustomChat>
Available Sub-Components
| Component | Description |
|---|---|
<Agent.Input /> |
Text input field |
<Agent.Submit> |
Submit button |
<Agent.Response /> |
Response display |
<Agent.Loading> |
Loading indicator (only shows when loading) |
<Agent.Error /> |
Error display (only shows on error) |
<Agent.History /> |
Chat history |
<Agent.Clear> |
Clear history button |
Custom Response Rendering
<Agent.Response render={(content) => <Markdown>{content}</Markdown>} />
Agent API Reference
Agent Props
| Prop | Type | Default |
|---|---|---|
id |
string | "default-agent" |
purpose |
string | "You are a helpful AI assistant." |
model |
string | "claude-sonnet-4-20250514" |
temperature |
number | 0.7 |
maxTokens |
number | 1000 |
streaming |
boolean | true |
memory |
boolean | false |
memoryDepth |
number | 100 |
onResponse |
(text: string) => void | - |
onError |
(error: Error) => void | - |
Configuration
Add your API key to .env:
VITE_ANTHROPIC_API_KEY=sk-ant-api03-xxxxx
API Reference
Special Tags
| Tag | Purpose | Example |
|---|---|---|
<state> |
Declare reactive state | <state count={0} /> |
<script> |
Component logic |
<script>const fn = () => {}</script>
|
<if> |
Conditional rendering |
<if condition={expr}>...</if>
|
<loop> |
List rendering |
<loop of={arr} let={item}>...</loop>
|
<switch> |
Switch statement |
<switch on={val}><case when="x">...</case></switch>
|
<Router> |
Client-side routing |
<Router><route path="/">...</route></Router>
|
<route> |
Route definition |
<route path="/user/:id" let={params}>...</route>
|
<async> |
Async data handling |
<async promise={...}><then>...</then></async>
|
<props> |
Declare accepted props | <props name onClick /> |
<slot> |
Content insertion point |
<slot /> or <slot name="header" />
|
<onMount> |
Lifecycle hook |
<onMount>{...code...}</onMount>
|
<Agent.Chat /> |
One-liner chat UI | <Agent.Chat purpose="..." memory={true} /> |
<Agent.Ask> |
Single prompt/response |
<Agent.Ask purpose="...">...</Agent.Ask>
|
<Agent> |
Custom AI chat container |
<Agent purpose="..." memory={true}>...</Agent>
|