# Your First App
In the previous chapter we built a counter and learned the init/update/view
cycle. Now we will start building **Plushie Pad**, a live widget editor
that grows with you throughout this guide.
In this chapter we set up the pad's layout: a code editor on the left, a
preview area on the right, and a save button. We will make the preview
actually work in the next chapter. For now the focus is on the DSL and
how views are composed.
## The DSL: three equivalent forms
Before we look at the code, a quick note on how the Plushie DSL works. There
are three ways to set widget properties, and they all produce the same result.
**Keyword arguments** on the call line, **inline declarations** mixed with
children in the do-block, and **nested do-blocks** for struct-typed
properties like padding, all in one expression:
```elixir
column spacing: 8 do
padding do
top 16
bottom 8
end
text("hello", "Hello")
end
```
This is equivalent to:
```elixir
column spacing: 8, padding: %{top: 16, bottom: 8} do
text("hello", "Hello")
end
```
You can mix all three in the same expression. Block declarations override
keyword arguments when they conflict. Use whichever form reads best for the
situation. Throughout this guide we will use different forms depending on
context.
Widgets also have a programmatic struct API that you can use instead of the
macro-based DSL. The same column from above:
```elixir
alias Plushie.Widget.{Column, Text}
Column.new("my_col", spacing: 8, padding: %{top: 16, bottom: 8})
|> Column.push(Text.new("hello", "Hello"))
```
The struct API produces the same output as the DSL. It is useful for helper
functions, dynamic generation, or anywhere you prefer working with data
structures directly. We will use the DSL throughout this guide.
## The complete pad
Here is the full module for this chapter. Save it as `lib/plushie_pad.ex`
and we will walk through the key parts below.
```elixir
defmodule PlushiePad do
use Plushie.App
import Plushie.UI
alias Plushie.Event.WidgetEvent
def init(_opts) do
%{
source: "# Write some Plushie code here\n",
preview: nil,
error: nil
}
end
def update(model, %WidgetEvent{type: :input, id: "editor", value: source}) do
%{model | source: source}
end
def update(model, _event), do: model
def view(model) do
window "main", title: "Plushie Pad" do
column width: :fill, height: :fill do
row width: :fill, height: :fill do
text_editor "editor", model.source do
width {:fill_portion, 1}
height :fill
highlight_syntax "ex"
font :monospace
end
container "preview", width: {:fill_portion, 1}, height: :fill, padding: 16 do
if model.error do
text("error", model.error, color: :red)
else
if model.preview do
model.preview
else
text("placeholder", "Press Save to compile and preview")
end
end
end
end
row padding: 8 do
button("save", "Save")
end
end
end
end
end
```
Run it:
```bash
mix plushie.gui PlushiePad --watch
```
You should see the editor on the left with syntax highlighting and a
placeholder message on the right. The save button is there but does not do
anything yet. We will fix that in the next chapter.
## Walking through the code
### The model
`init/1` receives a keyword list passed via the `app_opts:` option when
starting the app (e.g. `Plushie.start_link(PlushiePad, app_opts: [key: val])`).
We do not need it yet, so we ignore it. The returned map becomes the initial
model. Here we track the editor source text and leave slots for a compiled
preview and an error message.
### text_editor
`text_editor` is a multi-line editing widget with syntax highlighting
support. The `content` argument seeds the initial text, and subsequent
changes arrive as `:input` events with the full content as the value.
The `highlight_syntax: "ex"` option enables Elixir syntax highlighting.
Some widgets hold renderer-side state (cursor position, scroll offset,
text selection). `text_editor`, `text_input`, `combo_box`, `scrollable`,
and `pane_grid` all fall into this category. These widgets need an explicit
string ID so the renderer can match them to their state across renders. If
the ID changes, the state resets. Layout widgets like `column` and `row`
have no renderer-side state, so auto-generated IDs work fine for them.
### The view
The view is a `window` containing a `column` that splits vertically into
a main content `row` and a toolbar `row` at the bottom.
- `{:fill_portion, 1}` gives the editor and preview equal width. Change one
to `{:fill_portion, 2}` and it takes twice the space. We cover sizing in
depth in [chapter 7](07-layout.md).
- The preview pane is wrapped in `container "preview"`. This named container
will matter later when we need to distinguish events from preview widgets
vs the pad's own widgets.
- `model.preview` will hold a compiled widget tree once we wire up
compilation. For now it is `nil`, so the placeholder text shows.
- An `if` without an `else` returns `nil`, which the layout filters out
automatically. This is a useful pattern throughout Plushie views for
conditionally showing widgets.
### Events
The editor emits `:input` events on every keystroke. We pattern-match on the
widget ID and update the model. The catch-all clause ignores everything
else, including save button clicks. We will wire those up in the next
chapter.
## Verify it
Add a test for the pad layout in `test/pad_test.exs`:
```elixir
defmodule PlushiePad.PadTest do
use Plushie.Test.Case, app: PlushiePad
test "pad has editor and preview panes" do
assert_exists("#editor")
assert_exists("#preview")
assert_text("#preview/placeholder", "Press Save to compile and preview")
end
end
```
This verifies the split-pane layout is rendering correctly.
## Try it
With the pad running and hot reload active:
- Type some Elixir code in the editor. The syntax highlighting updates as
you type.
- Change `{:fill_portion, 1}` to `{:fill_portion, 2}` on the editor pane.
Save the file. The editor takes twice the width.
- Add a second button to the toolbar row: `button("clear", "Clear")`. Save
and see it appear.
The pad is a shell right now, a text editor next to an empty preview. In
the next chapter, we will bring it to life by wiring up hot reload and code
compilation so you can write Plushie widgets and see them rendered instantly.
---
Next: [The Development Loop](04-the-development-loop.md)