DEV Community

Cover image for How Angular Signal Forms Handle Validation CSS Classes
kafeel ahmad
kafeel ahmad

Posted on

How Angular Signal Forms Handle Validation CSS Classes

Understanding CSS Classes in Angular Signal Forms

If you've started experimenting with Angular's new Signal Forms API, you might have experienced a small panic moment :

"Wait… where are ng-invalid, ng-touched, and the other familiar CSS classes?"

You're not alone.

For years, Angular developers have relied on these built-in status classes to:

  • Style validation errors
  • Trigger UI feedback
  • Integrate with design systems

So when they suddenly disappear, it feels like a step backward.

But it's not.

In fact, Signal Forms give us something better: explicit, fully controlled CSS class binding.

Let's break it down

Why Signal Forms Don't Auto-Apply ng-* Classes

Signal Forms were designed with predictability and explicitness in mind.

Instead of Angular automatically mutating the DOM with implicit CSS classes, Signal Forms require you to decide:

  • Which classes exist
  • When they are applied
  • What logic controls them

This avoids:

  • Hidden side effects
  • Implicit DOM mutations
  • Tight coupling to legacy CSS conventions

But don't worry — Angular didn't abandon us.

Reintroducing Status Classes (On Your Terms)

Angular provides a way to define your own CSS status classes using a global configuration.

You do this via a provider that tells Angular:

"Apply this class when this condition is true."

The Shape of the Configuration

At a high level, the configuration looks like this:

Copy{
classes: {
[className: string]: (state: Field<unknown>, element?: HTMLElement) => boolean;
}
}
  • Keys → CSS class names
  • Values → Predicate functions
  • Return valuetrue means "apply this class"

Example 1: Restoring Classic Validation Classes

Let's start with the familiar ones — renamed for clarity and originality.

Copyimport { provideSignalFormsConfig, DEFAULT_STATUS_CLASSES } from '@angular/forms';

export const appFormProviders = [
provideSignalFormsConfig({
classes: {
...DEFAULT_STATUS_CLASSES
}
})
];

This immediately brings back behavior similar to:

  • invalid
  • touched
  • dirty
  • pristine

Without Angular forcing them onto every field.

Example 2: Custom Error Styling Logic

Now let's do something we couldn't easily do before.

Apply a CSS class only when:

  • The field is invalid
  • And the user has interacted with it
CopyprovideSignalFormsConfig({
classes: {
'field-error-visible': (controlState) =>
controlState().invalid() && controlState().interacted()
}
});

Result

Your UI stays clean until the user actually engages with the form — no more premature red borders 👌

Example 3: Conditional Classes Based on Element Type

You can even apply classes based on the actual DOM element.

CopyprovideSignalFormsConfig({
classes: {
'multi-line-input': (, domNode) =>
domNode?.tagName.<span class="hljs-title function">toLowerCase() === 'textarea'
}
});

This is incredibly powerful for:

  • Design systems
  • Component libraries
  • Advanced form layouts

Comparison: Old Forms vs Signal Forms

Here's a quick breakdown 👇

None

Why This Is Actually a Big Upgrade

At first glance, losing automatic CSS classes feels uncomfortable.

But Signal Forms give us:

Explicit UI logic
Better separation of concerns
More predictable DOM behavior
Powerful conditional styling
Cleaner design system integration

Instead of Angular deciding for you, you're fully in control now.

Final Thoughts

Signal Forms don't remove functionality — they promote intentional design.

Once you start defining your own class logic:

  • You'll write clearer styling rules
  • Debugging becomes easier
  • Your forms scale better across large apps

The initial surprise fades quickly — and what's left is a cleaner, more expressive form system.

This article builds upon patterns shared by Roberto Hecker in his exploration of Signal forms

Author: Angular_with_Awais

Top comments (0)