diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md
new file mode 100644
index 000000000..0fa696815
--- /dev/null
+++ b/docs/recipes/client-server/fable.forms.md
@@ -0,0 +1,237 @@
+## Install dependencies
+
+First off, you need to create a SAFE app, [install the relevant dependencies](https://mangelmaxime.github.io/Fable.Form/Fable.Form.Simple.Bulma/installation.html), and wire them up to be available for use in your F# Fable code.
+
+1. Create a new SAFE app and restore local tools:
+```sh
+dotnet new SAFE
+dotnet tool restore
+```
+1. Add bulma to your project:
+follow [this recipe](../ui/add-bulma.md)
+
+1. Install Fable.Form.Simple.Bulma using Paket:
+```sh
+dotnet paket add Fable.Form.Simple.Bulma -p Client
+```
+
+1. Install bulma and fable-form-simple-bulma npm packages:
+```sh
+npm add fable-form-simple-bulma
+npm add bulma
+```
+
+## Register styles
+
+1. Rename `src/Client/Index.css` to `Index.scss`
+
+2. Update the import in `App.fs`
+
+ === "Code"
+ ```.fsharp title="App.fs"
+ ...
+ importSideEffects "./index.scss"
+ ...
+ ```
+ === "Diff"
+ ```.diff title="App.fs"
+ ...
+ - importSideEffects "./index.css"
+ + importSideEffects "./index.scss"
+ ...
+ ```
+
+3. Import bulma and fable-form-simple in `Index.scss`
+
+ ``` .scss title="Index.scss"
+ @import "bulma/bulma.sass";
+ @import "fable-form-simple-bulma/index.scss";
+ ...
+ ```
+
+2. Remove the Bulma stylesheet link from `./src/Client/index.html`, as it is no longer needed:
+
+ ``` { .diff title="index.html" }
+
+ -
+
+ ```
+
+## Replace the existing form with a Fable.Form
+
+With the above preparation done, you can use Fable.Form.Simple.Bulma in your `./src/Client/Index.fs` file.
+
+1. Open the newly added namespaces:
+
+ ``` { .fsharp title="Index.fs" }
+ open Fable.Form.Simple
+ open Fable.Form.Simple.Bulma
+ ```
+
+
+1. Create type `Values` to represent each input field on the form (a single textbox), and create a type `Form` which is an alias for `Form.View.Model`:
+
+
+ ``` { .fsharp title="Index.fs" }
+ type Values = { Todo: string }
+ type Form = Form.View.Model
+ ```
+
+1. In the `Model` type definition, replace `Input: string` with `Form: Form`
+
+ === "Code"
+ ``` { .fsharp title="Index.fs" }
+ type Model = { Todos: Todo list; Form: Form }
+ ```
+
+ === "Diff"
+ ``` { .diff title="Index.fs" }
+ -type Model = { Todos: Todo list; Input: string }
+ +type Model = { Todos: Todo list; Form: Form }
+ ```
+
+1. Update the `init` function to reflect the change in `Model`:
+
+ === "Code"
+ ``` { .fsharp title="Index.fs" }
+ let model = { Todos = []; Form = Form.View.idle { Todo = "" } }
+ ```
+
+ === "Diff"
+ ``` { .diff title="Index.fs" }
+ -let model = { Todos = []; Input = "" }
+ +let model = { Todos = []; Form = Form.View.idle { Todo = "" } }
+ ```
+
+1. Change `Msg` discriminated union - replace the `SetInput` case with `FormChanged of Form`, and add string data to the `AddTodo` case:
+
+ === "Code"
+ ``` { .fsharp title="Index.fs" }
+ type Msg =
+ | GotTodos of Todo list
+ | FormChanged of Form
+ | AddTodo of string
+ | AddedTodo of Todo
+ ```
+
+ === "Diff"
+ ``` { .diff title="Index.fs" }
+ type Msg =
+ | GotTodos of Todo list
+ - | SetInput of string
+ - | AddTodo
+ + | FormChanged of Form
+ + | AddTodo of string
+ | AddedTodo of Todo
+ ```
+
+1. Modify the `update` function to handle the changed `Msg` cases:
+
+ === "Code"
+ ``` { .fsharp title="Index.fs" }
+ let update (msg: Msg) (model: Model) : Model * Cmd =
+ match msg with
+ | GotTodos todos -> { model with Todos = todos }, Cmd.none
+ | FormChanged form -> { model with Form = form }, Cmd.none
+ | AddTodo todo ->
+ let todo = Todo.create todo
+ let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo
+ model, cmd
+ | AddedTodo todo ->
+ let newModel =
+ { model with
+ Todos = model.Todos @ [ todo ]
+ Form =
+ { model.Form with
+ State = Form.View.Success "Todo added"
+ Values = { model.Form.Values with Todo = "" } } }
+ newModel, Cmd.none
+ ```
+
+ === "Diff"
+ ``` { .diff title="Index.fs" }
+ let update (msg: Msg) (model: Model) : Model * Cmd =
+ match msg with
+ | GotTodos todos -> { model with Todos = todos }, Cmd.none
+ - | SetInput value -> { model with Input = value }, Cmd.none
+ + | FormChanged form -> { model with Form = form }, Cmd.none
+ - | AddTodo ->
+ - let todo = Todo.create model.Input
+ - let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo
+ - { model with Input = "" }, cmd
+ + | AddTodo todo ->
+ + let todo = Todo.create todo
+ + let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo
+ + model, cmd
+ - | AddedTodo todo -> { model with Todos = model.Todos @ [ todo ] }, Cmd.none
+ + | AddedTodo todo ->
+ + let newModel =
+ + { model with
+ + Todos = model.Todos @ [ todo ]
+ + Form =
+ + { model.Form with
+ + State = Form.View.Success "Todo added"
+ + Values = { model.Form.Values with Todo = "" } } }
+ + newModel, Cmd.none
+ ```
+
+
+1. Create `form`. This defines the logic of the form, and how it responds to interaction:
+
+ ``` { .fsharp title="Index.fs" }
+ let form : Form.Form =
+ let todoField =
+ Form.textField
+ {
+ Parser = Ok
+ Value = fun values -> values.Todo
+ Update = fun newValue values -> { values with Todo = newValue }
+ Error = fun _ -> None
+ Attributes =
+ {
+ Label = "New todo"
+ Placeholder = "What needs to be done?"
+ HtmlAttributes = []
+ }
+ }
+
+ Form.succeed AddTodo
+ |> Form.append todoField
+ ```
+
+1. In the function `todoAction`, remove the existing form view. Then replace it using `Form.View.asHtml` to render the view:
+
+ === "Code"
+ ``` { .fsharp title="Index.fs" }
+ let private todoAction model dispatch =
+ Form.View.asHtml
+ {
+ Dispatch = dispatch
+ OnChange = FormChanged
+ Action = Action.SubmitOnly "Add"
+ Validation = Validation.ValidateOnBlur
+ }
+ form
+ model.Form
+ ```
+ === "Diff"
+ ``` { .diff title="Index.fs" }
+ let private todoAction model dispatch =
+ - Html.div [
+ - ...
+ - ]
+ + Form.View.asHtml
+ + {
+ + Dispatch = dispatch
+ + OnChange = FormChanged
+ + Action = Action.SubmitOnly "Add"
+ + Validation = Validation.ValidateOnBlur
+ + }
+ + form
+ + model.Form
+ ```
+
+
+## Adding new functionality
+
+With the basic structure in place, it's easy to add functionality to the form. For example, the [changes](https://github.com/CompositionalIT/safe-fable-form/commit/6342ee8f4abcfeed6dd5066718e6845e6e2174d0) necessary to add a high priority checkbox are pretty small.
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 437862b2d..5eb916c81 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -116,6 +116,8 @@ nav:
- Get data from the server: "recipes/client-server/messaging.md"
- Post data to the server: "recipes/client-server/messaging-post.md"
- Share code between the client and the server: "recipes/client-server/share-code.md"
+ - Add support for Fable.Forms: "recipes/client-server/fable.forms.md"
+
- FAQs:
- Moving from dev to prod: "faq/faq-build.md"
- Troubleshooting: "faq/faq-troubleshooting.md"