Frontend

Frontend Monthly #2 – Panda CSS and Angular with if-else support

The heat is pouring from the sky, which means we’ve entered the hot season. However, the frontend world seems to ignore this fact, because in the past month we got a new CSS-in-JS library from the creators of Chakra UI, a real if-else statement for Angular and an alternative to Hotjar from Microsoft.

Article cover

Panda CSS – Universal, Type-Safe, CSS-in-JS

A lot of time has passed since any CSS-in-JS library made as much noise as Panda CSS. The new library is created by Chakra UI maintainers (reminder: Chakra UI isone of the most popular component libraries for React). It combines the best of the most popular CSS solutions on the market. But before we get to the discussion of specifics, let’s take a moment to consider why do we really need another CSS-in-JS library.

For styling Chakra UI uses the popular Emotion library. However, it comes with two important consequences. First, this library needs to load a lot of code on the client side to start working. Second, this library cannot be used in conjunction with React Server Components – the brand new React feature, that everyone is talking about (including us, of course 😉). These two factors began to bother Chakra UI more and more over time, so they decided to create their own CSS-in-JS library.

Interestingly, MUI (formerly Material UI) is also facing an identical problem – will it migrate to Panda CSS? Time will tell, but we will definitely tell you about it in our reports 😉

At first glance, Panda CSS is not much different from Emotion. However, the devil is in the details. Emotion generates unique class names for each call to the css() method. Panda CSS breaks down the object passed to the css() method by keys at build time and generates classes strongly resembling those from Tailwind. Thanks to the fact that the class names are generated at the build stage, Panda CSS does not need additional client-side JavaScript code and can aggressively optimize the amount of CSS sent to the client.

import { css } from '../styled-system/css'

function App() {
  return (
    //👇 Example usage of Panda CSS
    <div className={css({
      backgroundColor: 'gainsboro',
      borderRadius: '9999px',
      fontSize: '13px',
      padding: '10px 15px'
    })}>
      <p>Hello World</p>
    </div>
  );
}
/* Below file represents CSS output sent to the client */

.bg_gainsboro {
  background-color: gainsboro;
}
 
.rounded_9999px {
  border-radius: 9999px;
}

.fs_13px {
  font-size: 13px;
}
 
.p_10px_15px {
  padding: 10px 15px;
}

Panda CSS is not only zero runtime, but also a bunch of interesting improvements. Let’s start with Patterns, which is a pack of “appendages” thanks to which you will never again have to wonder how to center a div or what the flex syntax exactly looks like.

import { center } from '../styled-system/patterns'
 
function App() {
  return (
    <div className={center({ bg: 'red.200' })}>
      <Icon />
    </div>
  )
}
import { flex } from '../styled-system/patterns'
 
function App() {
  return (
    <div className={flex({ direction: 'row', align: 'center' })}>
      <div>First</div>
      <div>Second</div>
      <div>Third</div>
    </div>
  )
}

Another interesting feature is “Recipies”, which are heavily inspired by the Class Variance Authority library. With them, we can define many different component variants and then compose while maintaining full type safety. The syntax seems a bit complicated , but the standardized way to create variants that are readable by our customers seems worth it.

import { cva } from '../styled-system/css'
 
const button = cva({
  base: {
    display: 'flex'
  },
  variants: {
    visual: {
      solid: { bg: 'red.200', color: 'white' },
      outline: { borderWidth: '1px', borderColor: 'red.200' }
    },
    size: {
      sm: { padding: '4', fontSize: '12px' },
      lg: { padding: '8', fontSize: '24px' }
    }
  }
})
import { button } from './button'
 
const Button = () => {
  return (
    <button className={button({ visual: 'solid', size: 'lg' })}>
      Click Me
    </button>
  )
}

The last new feature worth mentioning is Design Tokens. Importantly, this is a rather advanced implementation, as they can easily refer to each other.

export default defineConfig({
  theme: {
    // 👇 Define your tokens here
    tokens: {
      colors: {
        primary: { value: '#0FEE0F' },
        secondary: { value: '#EE0F0F' }
      },
      fonts: {
        body: { value: 'system-ui, sans-serif' }
      }
    }
  }
})
import { css } from '../styled-system/css'
 
function App() {
  return (
    <p
      className={css({
        color: 'primary', // 👈 So you can later use them like this
        fontFamily: 'body'
      })}
    >
      Hello World
    </p>
  )
}

Sources

Panda CSS – Docs

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

Angular – Built-In Control Flow

Angular has been evolving faster than ever in recent months. Less than a few weeks ago Angular 16 was releass, and with it a new reactive primitive in the form of Signals. Now the team behind the framework has unveiled another RFC, this time on Control Flow. If all goes according to plan, it will retire the structured directives we’ve known for a long time.

Wait, wait, wait? What’s wrong with the long-known structured directives? If you don’t know what’s going on, it’s probably about zone.js, or rather its removal. As a reminder, zone.js is a small library that is one of the cornerstones of Angular. In simple terms, it is responsible for intercepting asynchronous browser events and informing Angular of them. Then Angular runs a change detection mechanism. As uncle Ben used to say – with great power comes great responsibility. Unfortunately zone.js is not handling this responsibility well as most developer blame it for poor performance of Angular apps. Currently, the team at Google is working up a sweat to enable developers to create applications without zone.js. Unfortunately structured directives are so tied to the zone.js architecture that the team decided it would be best to implement something completely new and make up for the shortcomings of structured directives in the process.

The new syntax is heavily inspired by Svelte and, interestingly, it is 100% compatible with the HTML standard. It is worth noting that the new syntax is not just a syntactic sugar, but also a real improvement for developers. If statement finally becomes a full-fledged If-else statement (and no one will tell me that the old template-based solution was readable), switch statement similarly to TypeScript will be able to narrow types, and for-loop will require a track parameter making it hard to implement a non-optimal loop.

Currently (If Statement)

<div *ngIf="cond.expr(); else elseBlock">
  Main case was true!
</div>
<ng-template #elseBlock>
  <div *ngIf="other.expr(); else elseElseBlock">
    Extra case was true!
  </div>
  <ng-template #elseElseBlock>
    <div>False case!</div>
  </ng-template>
</ng-template>

RFC (If Statement)

{#if cond.expr}
  Main case was true!
{:else if other.expr}
  Extra case was true!
{:else}
  False case!
{/if}

Currently (Switch Statement)

<ng-container [ngSwitch]="cond.kind()">
   <ng-container *ngSwitchCase="x">X case</ng-container>
   <ng-container *ngSwitchCase="y">Y case</ng-container>
   <ng-container *ngSwitchCase="z">Z case</ng-container>
   <ng-container *ngSwitchDefault>No case matched</ng-container>
</ng-container>

RFC (Switch Statement)

{#switch cond.kind}
  {:case x}
    X case
  {:case y}
    Y case
  {:case z}
    Z case
  {:default}
    No case matched
{/switch}

Currently (For Statement)

<ng-container *ngFor="let item of items; trackBy: trackByFn">
  {{item}}
</ng-container>
<ng-container *ngIf="items.length === 0">
  There were no items in the list.
</ng-container>

RFC (For Statement)

{#for item of items; track item.id}
  {{ item }}
{:empty}
  There were no items in the list.
{/for}

In addition to alternatives to the structured directives we are already familiar with, the Angular team has also prepared an RFC for a brand new functionality – lazy loading of template fragments. The new “directive” offers everything you might need for lazy loading – advanced loading strategies, support for loading and error states, or definition of minimum display time for each state, in order to avoid blinking.

{#defer on interaction, timer(5s)}
  <calendar-cmp />
{:placeholder minimum 500ms}
  <img src="placeholder.png" />
{:loading}
  <div class="loading">Loading the calendar...</div>
{:error}
  <p> Failed to load the calendar </p>
  <p><strong>Error:</strong> {{$error.message}}</p>
{/defer}

Sources

RFC: Built-In Control Flow
RFC: Deferred Loading

TypeScript 5.1

The last few versions of TypeScript have brought us some real features. In version 4.9 we got the new keyword satisfies. In version 5.0, the entire codebase was rewritten from archaic namespaces to modules, bringing big performance improvements. Unfortunately, version 5.1 doesn’t bring too many interesting innovations….

The biggest new feature in TypeScript 5.1 is better type inference for functions that do not contain a return statement. In pure JavaScript, if a function does not contain a return statement then it returns an undefined value. In TypeScript, if a function does not contain a return statement then it must be typed as void or any. This minor difference in behavior could sometimes be annoying, but fortunately TypeScript 5.1 fixes it entirely.

// ✅ fine - we inferred that 'f1' returns 'void'
function f1() {
    // no returns
}
// ✅ fine - 'void' doesn't need a return statement
function f2(): void {
    // no returns
}
// ✅ fine - 'any' doesn't need a return statement
function f3(): any {
    // no returns
}
// ❌ error in TypeScript 5.0 
//   |A function whose declared type is neither |
//   |'void' nor 'any' must return a value.     |
// ✅ fine in TypeScript 5.1
function f4(): undefined {
    // no returns
}

Another novelty is the complete separation of types for setters and getters. It is true that until now they could be different, but there was a very specific contract between the two types. From now on, this contract is no longer relevant and they can be completely unrelated types.

// ❌ error in TypeScript 5.0 
// ✅ fine in TypeScript 5.1
interface CSSStyleRule {
    // ...
    /** Always reads as a `CSSStyleDeclaration` */
    get style(): CSSStyleDeclaration;
    /** Can only write a `string` here. */
    set style(newValue: string);
    // ...
}

Sources

Announcing TypeScript 5.1

Microsoft Clarity

Last month, Microsoft shared with the world its solution for creating page heatmaps and recording user sessions. Importantly, it is free and the Redmond corporation promises it will remain so. Moreover, Microsoft is already using the tool extensively for internal projects. The next time you come to choose an analytics tool for your application, consider Microsoft Clarity as a free feature-limited alternative to Hotjar.

Sources

See what your users want—with Clarity

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

State of CSS 2023

A few weeks ago, another edition of the State of CSS survey hit the web. I repeat it every year and I’ll repeat it now: If you’re looking for something to quickly and efficiently catch up on what’s new in the CSS world over the past few years, State of CSS is the best place to do it. Each question links to official documentation, Can I Use, and sometimes to the best articles on the topic. In summary, not only you have the opportunity to contribute for the good of the community, but also to learn something new effectively and quickly.

Come on! Please fill out the survey!

Sources

State of CSS 2023