Chapter 2: Your First Element
The $ factory is how you create HTML elements in Effex. Let’s explore how it works and build the basic structure of our todo app.
The $ Factory
The $ object has a method for every HTML element:
$.div() // <div></div>
$.span() // <span></span>
$.button() // <button></button>
$.input() // <input />
$.ul() // <ul></ul>
$.li() // <li></li>
// ... and so on
Adding Content
Pass content as arguments:
// Text content
$.h1({}, $.of("My Todo App"))
// Multiple children
$.div({},
collect(
$.h1({}, $.of("My Todo App")),
$.p({}, $.of("A simple todo list"))
)
)
Adding Attributes
Pass an attributes object as the first argument:
// With attributes
$.input({ type: "text", placeholder: "What needs to be done?" })
// Attributes + content
$.button({ class: "btn-primary" }, $.of("Add Todo"))
// Attributes + children
$.div({ class: "container" },
collect(
$.h1({}, $.of("My Todo App")),
$.p({}, $.of("A simple todo list"))
)
)
Common attributes:
class- CSS classes (string or array)id- Element IDstyle- Inline styles (object)onClick,onInput, etc. - Event handlers
Building Our Todo App Structure
Let’s create the basic HTML structure for our todo app. Update src/main.ts:
import { Effect } from "effect";
import { $, collect, mount, runApp } from "@effex/dom";
const container = document.getElementById("root");
if (!container) throw new Error("Root element not found");
const App = () => $.div({ class: "todo-app" },
collect(
// Header
$.header({ class: "header" },
collect(
$.h1({}, $.of("todos")),
$.input({
class: "new-todo",
placeholder: "What needs to be done?",
autofocus: true,
}),
),
),
// Main section (todo list)
$.main({ class: "main" },
$.ul({ class: "todo-list" },
collect(
// We'll make this dynamic later
$.li({ class: "todo-item" },
collect(
$.input({ type: "checkbox", class: "toggle" }),
$.span({ class: "todo-text" }, $.of("Learn Effex")),
),
),
$.li({ class: "todo-item" },
collect(
$.input({ type: "checkbox", class: "toggle" }),
$.span({ class: "todo-text" }, $.of("Build a todo app")),
),
),
),
),
),
// Footer
$.footer({ class: "footer" },
$.span({ class: "todo-count" }, $.of("2 items left")),
),
),
);
runApp(
Effect.gen(function* () {
yield* mount(App(), container);
}),
);
Save and check your browser. You should see a basic todo app structure!
Adding Some Styles
Create a file src/styles.css:
.todo-app {
max-width: 500px;
margin: 40px auto;
font-family: system-ui, sans-serif;
}
.header h1 {
font-size: 48px;
color: #b83f45;
text-align: center;
margin-bottom: 20px;
}
.new-todo {
width: 100%;
padding: 16px;
font-size: 18px;
border: 1px solid #ccc;
border-radius: 4px;
}
.todo-list {
list-style: none;
padding: 0;
margin: 20px 0;
}
.todo-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #eee;
}
.todo-text {
flex: 1;
}
.toggle {
width: 20px;
height: 20px;
}
.footer {
color: #777;
font-size: 14px;
}
Import it in your src/main.ts:
import "./styles.css";
import { Effect } from "effect";
// ... rest of imports
Nesting and Composition
Notice how elements nest naturally:
$.div({ class: "parent" },
$.div({ class: "child" },
$.span({}, $.of("Deeply nested content"))
)
)
This creates:
<div class="parent">
<div class="child">
<span>Deeply nested content</span>
</div>
</div>
Class Arrays
The class attribute accepts arrays for cleaner conditional classes:
// These are equivalent:
$.div({ class: "btn btn-primary btn-large" })
$.div({ class: ["btn", "btn-primary", "btn-large"] })
This becomes useful when classes are conditional (we’ll see this later with reactivity), or when using libraries like Tailwind.
What We’ve Built
We now have:
- A header with a title and input field
- A main section with a hardcoded todo list
- A footer showing the count
But it’s all static! In the next chapter, we’ll add reactivity with Signals to make our todos dynamic.
Key Takeaways
$is a factory with methods for all HTML elements- Pass attributes first, then content
- Use
collect(...)to pass multiple children - Use
$.of("text")for text content - Elements nest naturally - just put elements inside elements