I maintain LiberaUI, a small MIT-licensed Angular component library. When Angular made zoneless change detection a first-class option, I assumed my components would just work. They didn't, and the bugs were quiet ones: no errors, just a screen that stopped updating.
Here are the three patterns that broke, how I found them, and the tests that now keep them fixed.
1. Values set from code never appeared
Every form control in the library implements ControlValueAccessor. With zone.js this was enough:
writeValue(value: string | null) { this.value = value; } Without zone.js, control.setValue('x') called writeValue, the field changed, and the input on screen stayed empty. Nothing told Angular the view was dirty, because nothing in a template had triggered the change.
The fix is one line, and it works on every Angular version the library supports:
private readonly changeDetector = inject(ChangeDetectorRef); writeValue(value: string | null) { this.value = value; this.changeDetector.markForCheck(); } The same applies to setDisabledState. A form that disables itself after a request was silently ignoring it.
2. Anything after a timer or a promise
A copy button showed "Copied!" after await navigator.clipboard.writeText(...), and an entrance animation flipped its state inside setTimeout. Both changed a plain field after the click handler had finished, so in a zoneless app the text never appeared and the animated content stayed at opacity: 0.
For state that changes over time, a signal is the cleanest fix, because reading it in the template is enough for Angular to schedule an update:
readonly state = signal(''); private activate() { setTimeout(() => this.state.set(`${this.animation}-in`)); } 3. Tests that passed for the wrong reason
The original tests called fixture.detectChanges() after every action, which hides exactly this class of bug. The regression tests now run without zone.js and only wait for the app to settle:
TestBed.configureTestingModule({ imports: [HostComponent], // provideExperimentalZonelessChangeDetection() before Angular 20 providers: [provideZonelessChangeDetection()], }); fixture.componentInstance.control.setValue('updated'); await fixture.whenStable(); expect(input.value).toBe('updated'); When I first ran them, 8 of 8 form controls failed. That number is the reason I'd recommend writing these tests before you trust a library, including mine.
A note on accessibility
The same discipline paid off for keyboard and screen reader behavior. The menu follows the WAI-ARIA menu button pattern (arrow keys, Home/End, type-ahead, Escape returns focus), the data table supports multi-column sorting with Shift + Enter, and the breadcrumb marks the current page with aria-current while drawing separators in CSS so they are not read aloud. Each of these has a regression test, and the keyboard ones dispatch real key events.
Try it
- Docs and Theme Studio: https://liberaui.js.org
- Zoneless starter in StackBlitz: https://stackblitz.com/github/gioelevalori/liberaui/tree/master/examples/stackblitz
- Source: https://github.com/gioelevalori/liberaui
If you maintain components of your own, I'd be curious which zoneless bugs you ran into. And if you try LiberaUI, issues and feedback are very welcome.

