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 value →
truemeans "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 👇

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)