diff --git a/adev/src/content/examples/signal-forms/src/login-simple/app/app.component.ts b/adev/src/content/examples/signal-forms/src/login-simple/app/app.component.ts
new file mode 100644
index 00000000000..e1004498c43
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-simple/app/app.component.ts
@@ -0,0 +1,20 @@
+import {Component, signal} from '@angular/core';
+import {form, Field} from '@angular/forms/signals';
+
+/**
+ * @title Signal Forms - Login Example
+ */
+@Component({
+ selector: 'app-root',
+ templateUrl: 'app.component.html',
+ styleUrl: 'app.component.css',
+ imports: [Field],
+})
+export class App {
+ loginModel = signal({
+ email: '',
+ password: '',
+ });
+
+ loginForm = form(this.loginModel);
+}
diff --git a/adev/src/content/examples/signal-forms/src/login-simple/index.html b/adev/src/content/examples/signal-forms/src/login-simple/index.html
new file mode 100644
index 00000000000..34474578c22
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-simple/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Signal Forms - Login Example
+
+
+
+
+
+
+
+
diff --git a/adev/src/content/examples/signal-forms/src/login-simple/main.ts b/adev/src/content/examples/signal-forms/src/login-simple/main.ts
new file mode 100644
index 00000000000..8f970447f48
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-simple/main.ts
@@ -0,0 +1,5 @@
+import {bootstrapApplication} from '@angular/platform-browser';
+
+import {App} from './app/app.component';
+
+bootstrapApplication(App);
diff --git a/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.css b/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.css
new file mode 100644
index 00000000000..2b9a3100a69
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.css
@@ -0,0 +1,70 @@
+form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ max-width: 400px;
+ padding: 1rem;
+ font-family:
+ Inter,
+ system-ui,
+ -apple-system,
+ sans-serif;
+}
+
+div {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ font-weight: 500;
+}
+
+input {
+ padding: 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 1rem;
+ font-family: inherit;
+}
+
+input:focus {
+ outline: none;
+ border-color: #4285f4;
+}
+
+button {
+ padding: 0.75rem 1.5rem;
+ background-color: #4285f4;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ font-size: 1rem;
+ font-family: inherit;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+button:hover {
+ background-color: #357ae8;
+}
+
+button:active {
+ background-color: #2a65c8;
+}
+
+.error-list {
+ color: red;
+ font-size: 0.875rem;
+ margin: 0.25rem 0 0 0;
+ padding-left: 0;
+ list-style-position: inside;
+}
+
+.error-list p {
+ margin: 0;
+}
diff --git a/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.html b/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.html
new file mode 100644
index 00000000000..73ec4635295
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.html
@@ -0,0 +1,33 @@
+
diff --git a/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.ts b/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.ts
new file mode 100644
index 00000000000..23d4d9009ca
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-validation/app/app.component.ts
@@ -0,0 +1,34 @@
+import {Component, signal} from '@angular/core';
+import {form, Field, required, email, submit} from '@angular/forms/signals';
+
+/**
+ * @title Login Form with Validation
+ */
+@Component({
+ selector: 'app-root',
+ templateUrl: 'app.component.html',
+ styleUrl: 'app.component.css',
+ imports: [Field],
+})
+export class App {
+ loginModel = signal({
+ email: '',
+ password: '',
+ });
+
+ loginForm = form(this.loginModel, (p) => {
+ required(p.email, {message: 'Email is required'});
+ email(p.email, {message: 'Enter a valid email address'});
+ required(p.password, {message: 'Password is required'});
+ });
+
+ onSubmit(event: Event) {
+ event.preventDefault();
+ submit(this.loginForm, async () => {
+ // Perform login logic here
+ const credentials = this.loginModel();
+ console.log('Logging in with:', credentials);
+ // e.g., await this.authService.login(credentials);
+ });
+ }
+}
diff --git a/adev/src/content/examples/signal-forms/src/login-validation/index.html b/adev/src/content/examples/signal-forms/src/login-validation/index.html
new file mode 100644
index 00000000000..3258da4d93e
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-validation/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Signal Forms - Login with Validation
+
+
+
+
+
+
+
+
diff --git a/adev/src/content/examples/signal-forms/src/login-validation/main.ts b/adev/src/content/examples/signal-forms/src/login-validation/main.ts
new file mode 100644
index 00000000000..8f970447f48
--- /dev/null
+++ b/adev/src/content/examples/signal-forms/src/login-validation/main.ts
@@ -0,0 +1,5 @@
+import {bootstrapApplication} from '@angular/platform-browser';
+
+import {App} from './app/app.component';
+
+bootstrapApplication(App);
diff --git a/adev/src/content/introduction/essentials/BUILD.bazel b/adev/src/content/introduction/essentials/BUILD.bazel
index 4a238832929..92710491604 100644
--- a/adev/src/content/introduction/essentials/BUILD.bazel
+++ b/adev/src/content/introduction/essentials/BUILD.bazel
@@ -16,6 +16,7 @@ generate_guides(
"//adev/src/assets/images:signals.svg",
"//adev/src/assets/images:templates.svg",
"//adev/src/assets/images:what_is_angular.svg",
+ "//adev/src/content/examples",
],
mermaid_blocks = True,
visibility = ["//adev:__subpackages__"],
diff --git a/adev/src/content/introduction/essentials/signal-forms.md b/adev/src/content/introduction/essentials/signal-forms.md
new file mode 100644
index 00000000000..f74089036af
--- /dev/null
+++ b/adev/src/content/introduction/essentials/signal-forms.md
@@ -0,0 +1,237 @@
+
+Signal Forms is built on Angular signals to provide a reactive, type-safe way to manage form state.
+
+
+## How it works
+
+### 1. Create a signal model
+
+When you create a form, you start by creating a signal that holds the state of your form:
+
+```ts
+const loginModel = signal({
+ email: '',
+ password: '',
+});
+```
+
+### 2. Pass the data model to `form()`
+
+Then, you pass your form model into the `form()` function to create a form tree that mirrors and enhances your model's structure:
+
+```ts
+form(loginModel);
+
+// Access fields directly by property name
+loginForm.email
+loginForm.password
+```
+
+### 3. Bind inputs with `[field]` directive
+
+Next, you bind your HTML inputs to the form using the `[field]` directive, which creates two-way binding between them:
+
+```html
+
+
+```
+
+As a result, user changes (such as typing in the field) automatically updates the form, and any programmatic changes update the displayed value as well:
+
+```ts
+// Update the value programmatically
+loginForm.email().value.set('alice@wonderland.com');
+
+// The model signal is also updated
+console.log(loginModel().email); // 'alice@wonderland.com'
+```
+
+NOTE: The `[field]` directive also syncs field state for attributes like `required`, `disabled`, and `readonly` when appropriate. You can read more in the upcoming in-depth guide.
+
+### 4. Read form field values with `value()`
+
+Finally, you can access field values as reactive signals by calling the field as a function and accessing its `value()`:
+
+```html
+
+
Email: {{ loginForm.email().value() }}
+```
+
+```ts
+// Get the current value
+const currentEmail = loginForm.email().value();
+```
+
+Here's a complete example:
+
+
+
+
+
+
+
+## Basic usage
+
+The `[field]` directive works with all standard HTML input types. Here are the most common patterns:
+
+### Text inputs
+
+Text inputs work with various `type` attributes and textareas:
+
+```html
+
+
+
+```
+
+#### Numbers
+
+Number inputs automatically convert between strings and numbers:
+
+```html
+
+
+```
+
+#### Date and time
+
+Date inputs store values as `YYYY-MM-DD` strings, and time inputs use `HH:mm` format:
+
+```html
+
+
+
+```
+
+If you need to convert date strings to Date objects, you can do so by passing the field value into `Date()`:
+
+```ts
+const dateObject = new Date(form.eventDate().value());
+```
+
+#### Multiline text
+
+Textareas work the same way as text inputs:
+
+```html
+
+
+```
+
+### Checkboxes
+
+Checkboxes bind to boolean values:
+
+```html
+
+
+```
+
+#### Multiple checkboxes
+
+For multiple options, create a separate boolean `field` for each:
+
+```html
+
+
+```
+
+### Radio buttons
+
+Radio buttons work similarly to checkboxes. As long as the radio buttons use the same `[field]` value, Signal Forms will automatically bind the same `name` attribute to all of them:
+
+```html
+
+
+```
+
+When a user selects a radio button, the form `field` stores the value from that radio button's `value` attribute. For example, selecting "Premium" sets `form.plan().value()` to `"premium"`.
+
+### Select dropdowns
+
+Select elements work with both static and dynamic options:
+
+```html
+
+
+
+
+
+```
+
+NOTE: Multiple select (`