Skip to content

Latest commit

 

History

378 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ngx-formidable

Angular form fields you can actually theme, configure and customize. Validated by whatever you already use.

Created with ❤️ by Cynthion

npm version Deploy Live Studio Angular ^22 MIT License GitHub Stars

Four ngx-formidable fields, a dropdown open among them, restyled from the library's defaults in eight steps, one CSS variable per step, with the :root block growing beside them

Eleven form fields, one decorator that puts labels, prefixes, hints and errors around them, and around two hundred CSS custom properties to make them look like your product instead of like a component library. It holds the model and renders the fields; the rules come from Vest, zod, Angular's own validators, or nothing at all.

  • Studio — a live form of every field. Theme it to your brand, configure the fields, and take away the CSS and the Angular template.
  • Specimen — every field, one change at a time: every state, every label position, every adornment and every panel, under any preset or your own theme.
  • Docs — the guides and references below, rendered in the browser from the same files. .documentation/ holds them, plus the design notes for maintainers.

Table Of Contents

Features

  • 🧩 Eleven fields, one decorator — input, textarea, select, dropdown, autocomplete, radio and checkbox group, date, time, toggle, slider. <formidable-field-decorator> puts the label, prefixes, suffixes, hints and errors around any of them, in six label positions.
  • ✅ Bring your own validator — Vest, zod, Angular's own, or none. Field, group and whole-form rules, with when a rule runs and when it reveals set separately.
  • 🎨 Themeable to the corner — ~200 CSS custom properties and no design system in your bundle. Rebrand from one variable; no SCSS hooks, no theme to initialise.
  • 🚀 Template-driven, no boilerplate — one directive on the <form>, ngModel all the way down. No store, no reactive-forms scaffolding, standalone or NgModule.
  • 🧠 Typed end to end — a DeepPartial model and a DeepRequired shape, so a typo in a model key or a rule target fails the build.
  • ⌨️ Accessible by default — full keyboard handling, managed focus, combobox, listbox, switch and group roles, and errors in an aria-live region.
  • 🛡️ Masking, dates and panels — ngx-mask on text fields, one token string for parsing and formatting a date or time, and panels that flip when there is no room and become a sheet on phones.
  • 🛠️ Extensible — BaseFieldDirective makes a field of your own decorated, validated and themed like a built-in one.
  • 🎛️ Studio — build the theme and the form against the real components in the browser, then copy out the CSS and the Angular template.

When To Pick This Over Angular Material

Material is a design system with a form library in it. This is a form library with no design opinion. That is the whole difference, and it cuts both ways.

You Want Pick
Fields that look like your brand, themed from CSS variables only ngx-formidable
Vest, zod or a schema validator driving the rules ngx-formidable
Masking, hints, six label positions and clickable adornments built in ngx-formidable
A form library that adds no design system to your bundle ngx-formidable
Material Design, and to look like it Angular Material
Components beyond forms — tables, dialogs, menus, navigation Angular Material
The CDK: overlays, drag and drop, virtual scroll, a11y utilities Angular Material
A large ecosystem, many maintainers and a long support horizon Angular Material

Installation

npm i @cynthion/ngx-formidable date-fns ngx-mask pikaday

The library does not validate, so it brings no validation library. Add one only if you want it — npm i vest for the validator that ships in @cynthion/ngx-formidable/vest, or wire your own.

Full instructions, including what each peer dependency is for: Getting Started.

Setup

Two paths, registering the same providers.

// main.ts — standalone
import { provideNgxFormidable } from '@cynthion/ngx-formidable';

bootstrapApplication(AppComponent, {
  providers: [...provideNgxFormidable()]
}).catch(console.error);
// app.module.ts — NgModule
import { NgxFormidableModule } from '@cynthion/ngx-formidable';

@NgModule({
  imports: [BrowserModule, NgxFormidableModule.forRoot()],
  bootstrap: [AppComponent]
})
export class AppModule {}

Both take an optional config, including app-wide defaults for what every template would otherwise repeat: the label position, the adornment alignment, the panel position and the form's reveal, required-marker and debounce settings. See Getting Started.

Then the stylesheet, which is imported separately because it is a stylesheet and not a provider:

// styles.scss
@use '@cynthion/ngx-formidable/styles/ngx-formidable';

That is the whole default theme. Redeclare whatever you want to change in your own :root afterwards:

:root {
  --formidable-color-field-border-focus: #0f766e; // rebrand from this one variable
  --formidable-field-height: 50px;
}

Your First Form

Declare the model, the shape and the rules in one *.form.ts — this one validates with Vest:

export interface User {
  name: string;
  birthdate: Date;
}

export type UserFormModel = DeepPartial<User>;
export type UserFormShape = DeepRequired<UserFormModel>;

/** Every key the model may carry, all required — a dev-mode typo check, not a validator. */
export const userFormShape: UserFormShape = { name: '', birthdate: new Date() };

export const userFormSuite = create((model: UserFormModel, field?: string) => {
  mode(Modes.ALL); // Vest 6 defaults to `EAGER`, which reports only a field's first failing message
  if (field) only(field); // the form asks about one target at a time

  test('name', 'Name is required.', () => {
    enforce(model.name).isNotBlank();
  });
});

Then the template:

<form
  formidableForm
  [formValue]="formValue$ | async"
  [formShape]="formShape"
  [formSuite]="formSuite"
  (formValueChange)="formValue$.next($event)"
  (validChange)="isValid$.next($event)"
  (ngSubmit)="onSubmit()">
  <formidable-field-decorator>
    <formidable-input-field
      formidableFieldErrors
      name="name"
      [markRequired]="true"
      [ngModel]="(formValue$ | async)?.name" />
    <div formidableFieldLabel>Name</div>
    <div formidableFieldHint>As it appears on your passport</div>
  </formidable-field-decorator>

  <formidable-field-decorator>
    <formidable-date-field
      formidableFieldErrors
      name="birthdate"
      [unicodeTokenFormat]="'dd.MM.yyyy'"
      [ngModel]="(formValue$ | async)?.birthdate" />
    <div
      formidableFieldLabel
      [position]="'border'">
      Birthdate
    </div>
  </formidable-field-decorator>

  <button type="submit">Submit</button>
</form>

Every rule runs asynchronously, so the form is still PENDING when ngSubmit fires — gate a submit on validChange rather than reading form.valid.

The whole walkthrough, with the component state and where each piece goes: Getting Started.

What's In The Box

Full API — every input, output, type and token — in the Component Catalogue.

Category Component Value
Text <formidable-input-field> string | null
<formidable-textarea-field> string | null
Options <formidable-select-field> string | null
<formidable-dropdown-field> string | null
<formidable-autocomplete-field> string | null
Option Groups <formidable-radio-group-field> string | null
<formidable-checkbox-group-field> string[]
Date & Time <formidable-date-field> Date | null
<formidable-time-field> Date | null
Values <formidable-toggle-field> boolean | null
<formidable-slider-field> number | null
Structural <formidable-field-decorator> —
<formidable-field-option> —
<formidable-field-errors> —
Category Directive
Form-Level formidableForm, formidableValidateWholeForm, plus the two that attach themselves to ngModel and ngModelGroup
Decoration formidableFieldLabel, formidableFieldLabelAdornment, formidableFieldPrefix, formidableFieldSuffix, formidableFieldHint, formidableFieldErrors, formidableFieldToggleIcon
Vest Entry Point formSuite, from @cynthion/ngx-formidable/vest

Documentation

Guides teach a topic; references list what it accepts.

Guide Covers
Getting Started Install, wiring, the stylesheet, a first form
Fields Options, panels, keyboard, dates and times, masking, focus
Decoration Labels, adornments, prefixes, suffixes, hints, required marker
Validation Targets, timing, conditional fields, Vest, zod, messages
Theming The default theme, what to override, worked examples
Studio Building a theme and a form in the browser, and exporting both
Custom Fields Building a field, an option or a validator of your own
Reference Lists
Components Every component, directive, token, type and interface
Theme Reference Every overridable --formidable-* custom property

Design notes for maintainers live in .documentation/tech/, and the repo's own conventions in .documentation/impl/.

Contributing

Contributions are welcome, see CONTRIBUTING.md.

License

Everything in this repository is licensed under the MIT License unless otherwise specified.

In plain English: use it commercially, modify it, ship it inside a closed-source product, sublicense it — nothing has to be published back. The one condition is that the copyright notice and the license text travel with any copy or substantial portion of the code. It comes with no warranty and no liability.

Every runtime peer dependency is permissive too — MIT, 0BSD or Apache-2.0, no copyleft anywhere — so adding this library puts no obligation on you beyond MIT's own notice.

Copyright (c) 2025 - present Christian Lüthold

About

A powerful Angular component library for building rich, validated forms.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages