# Playground

> The form below is running ngwr on this page — signal forms, validation copy and all. One click opens the same demo as a complete Angular 22 workspace on StackBlitz, and every live demo on this site opens the same way.

Source: https://ngwr.dev/start/playground  
Kind: Getting started

## Signal Forms, live

Nineteen ngwr value controls implement `FormValueControl` (or `FormCheckboxControl`) themselves, so `[formField]` writes the component's own `value` / `checked` model. There is no `ControlValueAccessor` in the library — not a thin one, none. Submit the form below with a field empty: the copy comes from `<wr-form-field>` through the `ngwr/i18n` `validation.*` catalog, so nothing here spells out an error message.

```angular-ts
import { JsonPipe } from '@angular/common';
import { Component, signal } from '@angular/core';
import { FormField, email, form, required, submit } from '@angular/forms/signals';

import { WrButton } from 'ngwr/button';
import { WrCheckbox } from 'ngwr/checkbox';
import { WrFormField } from 'ngwr/form';
import { WrInput } from 'ngwr/input';
import { WrOption, WrSelect } from 'ngwr/select';

interface Signup {
  email: string;
  team: string;
  agree: boolean;
}

@Component({
  selector: 'demo-root',
  imports: [JsonPipe, FormField, WrButton, WrCheckbox, WrFormField, WrInput, WrOption, WrSelect],
  template: `
    <form (submit)="reserve($event)" style="display: flex; flex-direction: column; gap: 1rem; max-width: 26rem">
      <wr-form-field label="Work email" hint="We'll never share it." required>
        <input wrInput type="email" placeholder="ada@example.com" [formField]="signup.email" />
      </wr-form-field>

      <wr-form-field label="Team size" required>
        <wr-select ariaLabel="Team size" placeholder="Pick one" [formField]="signup.team">
          <wr-option value="solo">Just me</wr-option>
          <wr-option value="small">2–10 people</wr-option>
          <wr-option value="large">More than 10</wr-option>
        </wr-select>
      </wr-form-field>

      <wr-form-field>
        <wr-checkbox [formField]="signup.agree">I agree to the terms</wr-checkbox>
      </wr-form-field>

      <button wr-btn type="submit" color="primary" style="align-self: flex-start">Reserve your spot</button>

      @if (submitted(); as value) {
        <pre>{{ value | json }}</pre>
      }
    </form>
  `,
})
export class Demo {
  private readonly model = signal<Signup>({ email: '', team: '', agree: false });

  // Two bindings and nothing in between: `[formField]` writes the component's
  // own `value` / `checked` model, because each of these ngwr controls implements
  // `FormValueControl` itself. There is no ControlValueAccessor in the library.
  protected readonly signup = form(this.model, path => {
    required(path.email);
    email(path.email);
    required(path.team);
    required(path.agree);
  });

  protected readonly submitted = signal<Signup | null>(null);

  protected reserve(event: Event): void {
    event.preventDefault();
    void submit(this.signup, () => {
      this.submitted.set(this.model());
      // undefined means "the server found nothing wrong" — a real action would
      // POST here and map a 4xx onto the field it belongs to.
      return Promise.resolve(undefined);
    });
  }
}
```

Give it about two and a half minutes

The button hands StackBlitz a real workspace, and a real workspace has to install before it can serve — around 430 packages, cold, then roughly half a minute of Angular build. There is no progress bar for the install; the terminal is the progress bar. If you want it running in ten seconds instead, the three commands below do it locally.

## What the sandbox contains

A `@angular/build:application` workspace whose dependency ranges are read from the Angular and ngwr versions this site was built with — carets, so the install tracks the current patch rather than freezing on the day it was written.

- `ngwr` and `@angular/cdk` installed, with `@use 'ngwr';` in `src/styles.scss` — the whole token layer and every component's styles from one import.
- A zoneless bootstrap: `provideZonelessChangeDetection()`, plus `provideWrOverlay()` — ngwr's own CDK overlay container, so it can never collide with another library's — and `provideWrDateAdapter()`, which every calendar and date picker injects. They go in whether or not the demo uses them: each costs an import line, and a missing one is a blank page.
- The demo as `src/app/demo.ts`, character for character what the page shows. Snippets that are only a template fragment get a component generated around them, with the `imports` resolved from the library's own selector map.
- `ng serve` as `npm start`. StackBlitz installs the workspace from npm before it can serve it — around 430 packages, cold — so the first open takes a while, and how long is its container's call rather than ours.

```angular-ts
import { provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withDisabledInitialNavigation } from '@angular/router';

import { provideWrDateAdapter } from 'ngwr/date';
import { provideWrOverlay } from 'ngwr/overlay';

import { Demo } from './app/demo';

bootstrapApplication(Demo, {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZonelessChangeDetection(),
    provideRouter([], withDisabledInitialNavigation()),
    provideWrDateAdapter(),
    provideWrOverlay(),
  ],
}).catch(error => console.error(error));
```

```scss
// src/styles.scss — one import for the whole library.
@use 'ngwr';
```

## The same thing locally

`ng add ngwr` writes exactly this: it installs the peers, appends the stylesheet import, and prints the bootstrap for the options you pick.

```bash
# The same workspace on your machine — prompts for styles, date adapter,
# density and theme, then prints the bootstrap snippet you picked.
ng new my-app --style=scss
cd my-app
ng add ngwr
```
