DEV Community

Cover image for What's New in Taiga UI v5: A Modern Angular UI Kit
Barsukov Nikita for Angular

Posted on

What's New in Taiga UI v5: A Modern Angular UI Kit

Taiga UI is an Open Source Angular UI library that helps developers build modern, reliable user interfaces. We recently shipped the fifth major release, bringing a ton of architectural improvements and new capabilities. In this article, we'll take a deep dive into the highlights of version 5.0.0 and explain why you should plan your upgrade as soon as possible.

Fresh Minimum: Angular 19+

In this new major version of our libraries, we raised the minimum supported Angular version to 19 and above. This means we can take advantage of all the new features and improvements added since Angular 16 (the minimum supported version in the previous Taiga UI major). And the paradigm shift toward a signal-based style has changed a lot in our codebase.

Signal Inputs

All our hundreds of components and directives said goodbye to classic properties decorated with @Input() and switched to their signal-based counterpart — input. Previously, our code had a lot of getters that internally used a private class method decorated with @tuiPure for memoization. Here's a simplified example of that approach:

@Input({required: true})
fileName: string;

// Used in the template
// (potentially recalculated on every re-render)
get type(): string {
   return this.getType(this.file);
}

@tuiPure
private getType(file: string): string {
   const dot = file.lastIndexOf('.');

   return dot > 0 ? file.slice(dot) : '';
}
Enter fullscreen mode Exit fullscreen mode

Now, with signal-based computed, getters are no longer needed, and we can write cleaner, more readable code without extra optimizations. computed takes care of memoization automatically.

file = input.required<string>();

type = computed(() => {
   const dot = file.name.lastIndexOf('.');

   return dot > 0 ? file.name.slice(dot) : '';
});
Enter fullscreen mode Exit fullscreen mode

We're sending the @tuiPure decorator off into honorable retirement: you were a good friend and helper for many years — thank you!

Signal viewChild(-ren) / contentChild(-ren)

In Taiga components, we frequently used the @ViewChild(‑ren) and @ContentChild(‑ren) decorators. You decorate a property in a component — and the requested DOM element gets automatically assigned to it. A significant limitation of this approach was that these elements only became available in the AfterViewInit and AfterContentInit lifecycle hooks. As a result, constructs like this could pop up in our codebase:

@Component(...)
export class AnyComponent {
   private contentReady$ = new ReplaySubject<boolean>(1);

   children$ = this.contentReady$.pipe(
       switchMap(() => this.childrenQuery.changes),
   )

   @ContentChildren('ref')
   childrenQuery!: QueryList<any>;

   ngAfterContentInit() {
       this.contentReady$.next(true);
   }
}
Enter fullscreen mode Exit fullscreen mode

The contentReady$ stream notifies all its observers that it's safe to proceed. The trick of creating such a stream made the code slightly more declarative compared to directly manipulating all observers inside a hook. But it still produced its fair share of boilerplate.

Signal-based viewChild(-ren) and contentChild(-ren) are a real game-changer. You can practically forget about the AfterViewInit and AfterContentInit hooks, because signals created via viewChild and contentChild know when to update on their own, and all their subscribers automatically recalculate reactively. All those lines from the old approach are replaced by a single built-in one-liner.

Signals Over RxJS

Even though our team deeply respects RxJS and is confident it'll be around for a long time, we genuinely fell in love with signals. We decided to make them the priority in our public API — not just because of their growing popularity in the Angular community, but because for many reactive tasks they're a much better fit than RxJS, letting us provide better support and write more optimal, understandable code.

Here are just a few examples of changes from the new major version — the updated TUI_NUMBER_FORMAT and TUI_DATE_FORMAT.

// Before
inject(TUI_NUMBER_FORMAT) // Observable<TuiNumberFormatSettings>
inject(TUI_DATE_FORMAT) // Observable<TuiDateFormatSettings>

// After 
inject(TUI_NUMBER_FORMAT) // Signal<TuiNumberFormatSettings>
inject(TUI_DATE_FORMAT) // Signal<TuiDateFormatSettings>
Enter fullscreen mode Exit fullscreen mode

To sum up the thought about the dawn of the "signal era" in the Taiga codebase: we don't resist the new trends set by the Angular team. And most importantly, we don't prevent our users from seamlessly harnessing the full power of signals when building applications with Taiga UI!

We've already seen the benefits of the signal-based style firsthand. Less boilerplate, better performance, dramatically fewer Change Detection bugs, and improved support for Zoneless applications!

New Esbuild + Vite Build Engine

Another important change driven by raising the minimum Angular version is the switch to the modern Esbuild + Vite build engine. This move not only improved our own Developer Experience (build time during local development was significantly reduced), but also allowed us to catch bugs in YOUR Esbuild applications (the default for all new Angular apps) before we even release our libraries.

And these aren't just words — we've already fixed several esbuild incompatibility bugs related to circular dependencies (#12435 and #12438), as well as CSS selector specificity issues (#12544, #12543, #12553).

Control Flow

Before Control Flow appeared, the Taiga UI team had already been publishing the structural directive *tuiLet for many years, which allowed you to declare a local variable in a template. And as our company's codebase shows, this directive was used over 6,000 times. The local variable declaration approach was extremely popular. It has now been replaced by the new built-in @let syntax.

The Taiga *tuiLet directive is now gracefully retiring, making way for the built-in @let syntax — which, as a nice bonus, requires zero imports!

A Shiny New Component Showcase

We've significantly reworked the design and content of our documentation. It now features improved navigation and a more intuitive interface.

New documentation design

I'd also like to remind you that the engine behind our documentation is a reusable solution published as the npm package @taiga-ui/addon-doc. It's already actively used not only for Taiga UI docs, but also in other Taiga Family products: Maskito, Taiga Editor, Ng Morph, and Ng Draw Flow. This package keeps evolving and gaining new features (for example, a brand-new Table of Contents component was added in this release). You can always use @taiga-ui/addon-doc to build documentation for your own library!

Browser Bump

Here's a little secret: the favorite part of every major release for Taiga UI maintainers is bumping the minimum supported browser versions. It unlocks new capabilities and features that we happily use in our libraries to write even cleaner and more reliable code.

Already in Taiga UI v4, we set a course for adding RTL support to our libraries. That's right — we expanded our audience to include Middle Eastern and Central Asian products!
With the new browser support, we got access to an even wider list of logical CSS properties, such as inset, padding-inline, margin-inline, and many others.

RTL support has become even easier to maintain, and the code is more robust (since it's now handled by native logical CSS properties instead of our old fallbacks and workarounds).

We also got access to BigInt. It feels nice to finally have access to all JavaScript data types — at least by 2026! :D
We immediately put this to use and added BigInt support to our @maskito/kit library in the Number mask and in our Taiga InputNumber component.

Angular Animations — Goodbye!

Starting with Angular 20.2, the @angular/animations package has been marked as deprecated. And the official Angular documentation now reads as follows:

You can replace all animations based on @angular/animations with plain CSS or JS animation libraries. Removing @angular/animations from your application can significantly reduce the size of your JavaScript bundle. Native CSS animations generally offer superior performance, as they can benefit from hardware acceleration.

And we responded promptly. In Taiga UI v5, we completely dropped the @angular/animations package: all existing animations have been rewritten using pure native CSS, and @angular/animations has been entirely removed from the dependency list of our packages! No more mandatory provideAnimations() just to get Taiga UI up and running.

The trickiest part was natively rewriting the logic around the former :leave mechanism. Currently, there's no convenient CSS alternative for animating an element leaving the DOM without timers and boilerplate code. But my colleague found an elegant solution to this problem as well. The @taiga-ui/cdk library got a new Animated directive. Just slap this directive on any DOM element — when it appears or disappears, the element gets a CSS class tui-enter / tui-leave, and you can define any CSS animations for that class. All without code boilerplate and in the true Angular way!

It's also great that we're moving in the same direction as the Angular team. The Animated directive neatly anticipated the future syntax that the Angular team introduced very recently in their v21 announcementanimate.enter and animate.leave. Migrating from our solution to the built-in Angular syntax will be as simple as it gets — just replace the tuiAnimated directive on an element with animate.enter="tui-enter" and animate.leave="tui-leave" (and keep the CSS files unchanged).

A nice bonus: our Animated directive has been cherry-picked into the fourth major version of Taiga, so you can start gradually moving away from Angular animations in your app right now — even before you begin upgrading your Taiga UI version, and even if your app's Angular version is significantly older than 20.2. And when you upgrade Taiga to v5, @angular/animations will simply disappear from your node_modules.

Simplified Taiga UI Setup for Your Project

Inspired by the modern Angular naming conventions for DI utility functions (provideRouter, provideServerRendering, provideZoneChangeDetection, etc.), we decided to simplify the process of adding Taiga UI to your project. Now, to set up the DI tree, all you need is a single call to provideTaiga(), which automatically provides all the necessary dependencies and initial configurations for our components to work.

For you, it's just one function call, but under the hood it takes care of automatic font scaling, dark/light theme setup, and other internal configurations needed for Taiga UI components to function correctly!

New Text Fields Are Now a Stable API

We kicked off the large-scale text field refactoring back in the previous major version.
At that time, we moved all our old control versions into the @taiga-ui/legacy package (to ensure a smooth migration path for users to the new public API) and started rewriting each component from scratch using fresh design specs and modern Angular features (decomposition via host directives + signals).

The task turned out to be quite challenging and extremely labor-intensive, but the results will genuinely make you happy. The new text fields offer a declarative way to customize through HTML markup and a uniform public API across all types of controls. Once you understand the customization concepts for one type of text field, you can easily customize any other control — without even looking at the docs!

Another key feature of the new text fields is their openness. Everything you need is exposed, so you can bring even the wildest ideas to life. Just take a look at this InputDate example:

<tui-textfield>
  <label tuiLabel>Choose a date</label>
  <input tuiInputDate [formControl]="control" />

  <ng-container *tuiDropdown>
    <tui-calendar [markerHandler]="markerHandler" />

    <button
      tuiButton
      (click)="..."
    >
      Today
    </button>
  </ng-container>
</tui-textfield>
Enter fullscreen mode Exit fullscreen mode

Preview of InputDate with opened calendar

First, all the input parameters of the <tui-calendar /> component are fully at our disposal — no prop drilling like in the previous version of this control! Second, we can freely add any elements (even interactive ones!) to the calendar dropdown — for example, a "Today" button with a custom click handler!

And this applies to every type of text field (and there are over 15 of them!).
With the release of the fifth major version, our large-scale text field refactoring is officially complete. Their public API is fully stabilized, and the previous generation of these controls has been permanently removed from the @taiga-ui/legacy package.

For those who prefer gradual changes in life and code, we cherry-picked all important fixes for the new text fields into the fourth major version, making the migration to the new major as smooth as possible. Before upgrading to Taiga v5, you can iteratively update your codebase, mixing old and new text fields, so the upcoming migration is as comfortable and painless as it can be.

Improved Icon Component

In the previous major release announcement, I mentioned that for icons we moved away from the old approach of using <svg /> with the <use /> tag in favor of CSS masks.

Back then, the new approach was implemented in the Icon component and brought numerous benefits: the ability to store icons on a CDN and render them without DOM manipulations or a sanitizer, automatic browser caching, and a simplified way to scale icons.

But we didn't stop there and continued to develop this further.
Starting with the fifth major version, this component can do significantly more:

  • You can now control not only the icon size but also the stroke width of the icon's content. This capability is demonstrated really well in this interactive documentation example.

  • The updated component supports not only classic single-color icons but also multi-color and font-based icons! They're managed through the @tui, @img, and @font prefixes, and the component figures out under the hood how to render the icon based on its type: via CSS mask, background-image, or font + content on the :before pseudo-element. You can see it in action in this example.

And Much More!

  • Portals have been completely rewritten and streamlined into a single grid-based container. It's now even easier to customize dialogs, simpler to manage notifications and their subtypes (screen position, number of simultaneous instances), and easier to create your own custom portal entities through new abstract classes and services.

  • A new Popout service that simplifies displaying custom content in Picture-in-Picture mode or opening a piece of your application in an entirely new tab.

  • We stabilized the use of the CloseWatcher web API in Taiga dialogs. Now, when a dialog is open on an Android device and the user taps the browser's "Back" button, the dialog simply closes — instead of navigating to the previous route behind the open dialog.

  • A revamped Accordion. We're continuing the trend (that accompanies every Taiga major version) of minimal DOM element nesting in our components, which should make it easier for you to customize them and set native attributes like ARIA attributes or id — and now the accordion has joined the ranks of the "lucky ones."

  • Automatic font scaling is now enabled by default. Many users rely on iOS/Android system accessibility settings to increase the font size across all interfaces on their mobile devices — including web apps. Taiga components can read this system setting and adapt, making your interfaces comfortable for users with visual impairments.

  • We dropped the use of our custom decorators — you're free to build your application with any value of the TypeScript experimentalDecorators flag without worrying that Taiga UI components will stop working.

  • We're continuing to actively develop AI tooling support for working with Taiga UI components. Our MCP server now supports the new major version as well. We're also closely watching the development of WebMCP to integrate its support into our documentation when the time comes.

And a huge number of other improvements and new features that you can find in the release notes.

How to Upgrade?

We did our best to make the migration to the new version as smooth as possible. To that end, we've prepared a bunch of migration scripts that will automatically analyze your code and fix most breaking changes — all with a single console command! And anything that was too complex to fix automatically via AST manipulations has been annotated with comments right in the code — complete with hints and links so you can easily finish the migration on your own or with the help of AI agents.

Detailed instructions for running the migration schematics and the necessary preparation steps are available in our upgrade guide.

If you run into any issues or bugs during the upgrade, don't hesitate to open an issue or ask a question in our Telegram community. The Taiga team can't wait for your feedback!

See You Soon!

To wrap up, let me just give you a little sneak peek at our future plans.

Very soon you'll see revamped date picker components — our design team has already delivered fresh design specs with a more modern look for the calendars. During the rework, we plan to address the accumulated feature requests around their customization.

We're also keeping a close eye on the development of signal-based forms. As of this writing, the new API is still rough around the edges and continues to undergo changes. As soon as it fully stabilizes, we'll make sure Taiga controls support them.

Finally, one of our big goals for this year is to keep pushing the accessibility of our components forward! All Taiga components traditionally support keyboard navigation, screen readers, and other assistive technologies, but accessibility is a broader topic and we plan to dedicate even more attention to it this year.

See you in upcoming articles and releases!

And a reminder: the easiest way to say thank you is to give us a star on GitHub.

Top comments (0)