Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Thu, 15 Aug 2024 13:22:54 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 225069128 TUIs https://frontendmasters.com/blog/tuis/ https://frontendmasters.com/blog/tuis/#respond Thu, 15 Aug 2024 13:22:53 +0000 https://frontendmasters.com/blog/?p=3487 I mentioned lazygit the other day, a project I find to have a tremendously well done interface for being entirely at the command line. As frustrated as I get with tmux sometimes (the scrolling and the copying text, ughgkh), I still find it to be useful and impressive that “UI” can exist at all in the terminal.

I’ve since seen Posting, an API client, which also looks amazing. The Charm tools are pretty jaw dropping to me, which include stuff like a Markdown app and email sender. Turns out, there are actually a ton of “TUI” apps, collected here by Justin Garrison. An SQL client, a file manager, and an image viewer, are a few that caught my eye.

]]>
https://frontendmasters.com/blog/tuis/feed/ 0 3487
Fine-Grained Reactivity in Svelte 5 https://frontendmasters.com/blog/fine-grained-reactivity-in-svelte-5/ https://frontendmasters.com/blog/fine-grained-reactivity-in-svelte-5/#respond Wed, 14 Aug 2024 15:05:23 +0000 https://frontendmasters.com/blog/?p=3438 We’ve been looking at the up and coming Svelte 5. We looked at basic features like state, props, and side effects. Then we looked at Snippets, which is a lightweight feature Svelte added for re-using bits of HTML within (for now) a single component.

Article Series

In this post, we’ll take a close look at Svelte’s new fine-grained reactivity.

What is fine-grained reactivity?

The best way to describe fine-grained reactivity is to show what it isn’t, and the best example of non-fine grained reactivity is React. In React, in any component, setting a single piece of state will cause the entire component, and all of the descendent components to re-render (unless they’re created with React.memo). Even if the state you’re setting is rendered in a single, simple <span> tag in the component, and not used anywhere else at all, the entire world from that component on down will be re-rendered.

This may seem absurdly wasteful, but in reality this is a consequence of the design features that made React popular when it was new: the data, values, callbacks, etc., that we pass through our component trees are all plain JavaScript. We pass plain, vanilla JavaScript objects, arrays and functions around our components and everything just works. At the time, this made an incredibly compelling case for React compared to alternatives like Angular 1 and Knockout. But since then, alternatives like Svelte have closed the gap. My first post on Svelte 5 showed just how simple, flexible, and most importantly reliable Svlete’s new state management primitives are. This post will show you the performance wins these primitives buy us.

Premature optimization is still bad

This post will walk through some Svelte templates using trickery to snoop on just how much of a component is being re-rendered when we change state. This is not something you will usually do or care about. As always, write clear, understandable code, then optimize when needed (not before). Svelte 4 is considerably less efficient than Svelte 5, but still much more performant than what React does out of the box. And React is more than fast enough for the overwhelming majority of use cases — so it’s all relative.

Being fast enough doesn’t mean we can’t still look at how much of a better performance baseline Svelte now starts you off at. With a fast-growing ecosystem, and now an incredibly compelling performance story, hopefully this post will encourage you to at least look at Svelte for your next project.

If you’d like to try out the code we’ll be looking at in this post, it’s all in this repo.

Getting started

The code we’ll be looking at is from a SvelteKit scaffolded project. If you’ve never used SvelteKit before that’s totally fine. We’re not really using any SvelteKit features until the very end of this post, and even then it’s just re-hashing what we’ll have already covered.

Throughout this post, we’re going to be inspecting if and when individual bindings in a component are re-evaluated when we change state. There’s various ways to do this, but the simplest, and frankly dumbest, is to force some global, non-reactive, always-changing state into these bindings. What do I mean by that? In the root page that hosts our site, I’m adding this:

<script>
  var __i = 0;
  var getCounter = () => __i++;
</script>

This adds a global getCounter function, as well as the __i variable. getCounter will always return the next value, and if we stick a call to it in our bindings, we’ll be able to snoop on when those bindings are being re-executed by Svelte. If you’re using TypeScript, you can avoid errors when calling this like so:

declare global {
  interface Window {
    getCounter(): number;
  }
}

export {};

This post will look at different pages binding to the same data, declared mostly like this (we’ll note differences as we go).

let tasks = [
  { id: 1, title: "Task A", assigned: "Adam", importance: "Low" },
  { id: 2, title: "Task B", assigned: "Adam", importance: "Medium" },
  { id: 3, title: "Task C", assigned: "Adam", importance: "High" },
  { id: 4, title: "Task D", assigned: "Mike", importance: "Medium" },
  { id: 5, title: "Task E", assigned: "Adam", importance: "High" },
  { id: 6, title: "Task F", assigned: "Adam", importance: "High" },
  { id: 7, title: "Task G", assigned: "Steve", importance: "Low" },
  { id: 8, title: "Task H", assigned: "Adam", importance: "High" },
  { id: 9, title: "Task I", assigned: "Adam", importance: "Low" },
  { id: 10, title: "Task J", assigned: "Mark", importance: "High" },
  { id: 11, title: "Task K", assigned: "Adam", importance: "Medium" },
  { id: 12, title: "Task L", assigned: "Adam", importance: "High" },
];

And we’ll render these tasks with this markup:

<div>
  {#each tasks as t}
    <div>
      <div>
        <span>{t.id + getCounter()}</span>
        <button onclick={() => (t.id += 10)} class="border p-2">Update id</button>
     </div>
     <div>
       <span>{t.title + getCounter()}</span>
       <button onclick={() => (t.title += 'X')} class="border p-2">Update title</button>
     </div>
     <div>
        <span>{t.assigned + getCounter()}</span>
        <button onclick={() => (t.assigned += 'X')} class="border p-2">Update assigned</button>
      </div>
      <div>
        <span>{t.importance + getCounter()}</span>
        <button onclick={() => (t.importance += 'X')} class="border p-2">Update importance</button>
      </div>
    </div>
  {/each}
</div>

The Svelte 4 code we’ll start with uses the on:click syntax for events, but everything else will be the same.

The calls to getCounter inside the bindings will let us see when those bindings are re-executed, since the call to getCounter() will always return a new value.

Let’s get started!

Svelte 4

We’ll render the content we saw above, using Svelte 4.

Plain and simple. But now let’s click any of those buttons, to modify one property, of one of those tasks—it doesn’t matter which.

Notice that the entire component (every binding in the component) re-rendered. As inefficient as this seems, it’s still much better than what React does. It’s not remotely uncommon for a single state update to trigger multiple re-renders of many components.

Let’s see how Svelte 5 improves things.

Svelte 5

For Svelte 5, the code is pretty much the same, except we declare our state like this:

let tasks = $state([
  { id: 1, title: "Task A", assigned: "Adam", importance: "Low" },
  <em>// and so on ...</em>
  { id: 12, title: "Task L", assigned: "Adam", importance: "High" },
]);

We render the page, and see the same as before. If you’re following along in the repo, be sure to refresh the page after navigating, so the page will start over with the global counter.

Now let’s change one piece of state, as before. We’ll update the title for Task C, the third one.

Just like that, only the single piece of state we modified has re-rendered. Svelte was smart enough to leave everything else alone. 99% of the time this won’t make any difference, but if you’re rendering a lot of data on a page, this can be a substantial performance win.

Why did this happen?

This is the default behavior when we pass arrays and objects (and arrays of objects) into the $state rune, like we did with:

let tasks = $state([

Svelte will read everything you pass, set up Proxy objects to track what changes, and update the absolute minimum amount of DOM nodes necessary.

False Coda

We could end the post here. Use the $state primitive to track your reactive data. Svelte will make it deeply reactive, and update whatever it needs to update when you change anything. This will be just fine the vast majority of the time.

But what if you’re writing a web application that has to manage a ton of data? Making everything deeply reactive is not without cost.

Let’s see how we can tell Svelte that only some of our data is reactive. I’ll stress again, laboring over this will almost never be needed. But it’s good to know how it works if it ever comes up.

Rediscovering a long-lost JavaScript feature

Classes in JavaScript have gotten an unfortunately bad reputation. Classes are an outstanding way to declare the structure of a set of objects, which also happen to come with a built-in factory function for creating those objects. Not only that, but TypeScript is deeply integrated with them.

You can declare:

class Person {
  firstName: string;
  lastName: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

Not only will this provide you a factory function for creating instances of a Person, via new Person('Adam', 'Rackis'), but Person can also be used as a type within TypeScript. You can create variables or function parameters of type Person. It’s one of the few things that exist as a runtime construct and also a TypeScript type.

That said, if you find yourself reaching for extends in order to create deep inheritance hierarchies with classes, please please re-think your decisions.

Anyway, why am I bringing up classes in this post?

Fine-grained reactivity in Svelte 5

If you have a performance-sensitive section of code where you need to mark some properties as non-reactive, you can do this by creating class instances rather than vanilla JavaScript objects. Let’s define a Task class for our tasks. For the properties we want to be reactive, we’ll set default values with the $state() rune. For properties we don’t want to be reactive, we won’t.

class Task {
  id: number = 0;
  title = $state("");
  assigned = $state("");
  importance = $state("");

  constructor(data: Task) {
    Object.assign(this, data);
  }
}

And then we just use that class

let tasks = $state([
  new Task({ id: 1, title: "Task A", assigned: "Adam", importance: "Low" }),
  <em>// and so on</em>
  new Task({ id: 12, title: "Task L", assigned: "Adam", importance: "High" }),
]);

I simplified the class a bit by taking a raw object with all the properties of the class, and assigning those properties with Object.assign. The object literal is typed in the constructor as Task, the same as the class, but that’s fine because of TypeScript’s structural typing.

When we run that, we’ll see the same exact thing as before, except clicking the button to change the id will not re-render anything at all in our Svelte component. To be clear, the id is still changing, but Svelte is not re-rendering. This demonstrates Svelte intelligently not wiring any kind of observability into that particular property.

Side note: if you wanted to encapsulate / protect the id, you could declare id as #id to make it a private property and then expose the value with a getter function.

Going deeper

What if you don’t want these tasks to be reactive at the individual property at all? What if we have a lot of these tasks coming down, and you’re not going to be editing them? So rather than have Svelte set up reactivity for each of the tasks’ properties, you just want the array itself to be reactive.

You basically want to be able to add or remove entries in your array, and have Svelte update the tasks that are rendered. But you don’t want Svelte setting up any kind of reactivity for each property on each task.

This is a common enough use case that other state management systems support this directly, for example, MobX’s observable.shallow. Unfortunately Svelte does not have any such helper, as of yet. That said, it is currently being debated, so keep your eyes open for a $state.shallow() that would do what we’re about to show. But even if it does get added, implementing it ourselves will be a great way to kick the tires of Svelte’s new reactivity system. Let’s see how.

Implementing our own $state.shallow() equivalent

We already saw how passing class instances to an array shut off fine-grained reactivity by default, leaving you to opt-in, as desired, by setting class fields to $state(). But our data are likely coming from a database, as plain (hopefully typed) JavaScript objects, unrelated to any class; more importantly we likely have zero desire to cobble together a class just for this.

So let’s simulate it. Let’s say that a database is providing our Task objects as JS objects. We (of course) have a type for this:

type Task = {
  id: number;
  title: string;
  assigned: string;
  importance: string;
};

We want to put those instances into an array that itself is reactive, but not the individual properties on the tasks. With a tiny bit of cleverness we can make it mostly painless.

class NonReactiveObjectGenerator {
  constructor(data: unknown) {
    Object.assign(this, data);
  }
}

function shallowObservable<T>(data: T[]): T[] {
  let result = $state(data.map(t => new NonReactiveObjectGenerator(t) as T));
  return result;
}

Our NonReactiveObjectGenerator class takes in any object, and then smears all that object’s properties onto itself. And our shallowObservable takes an array of whatever, and maps it onto instances of our NonReactiveObjectGenerator class. This will force each instance to be a class instance, with nothing reactive. The as T is us forcing TypeScript to treat these new instances as whatever type was passed in. This is accurate, but something TypeScript needs help understanding, since it’s not (as of now) able to read and understand our call to Object.assign in the class constructor.

If you closely read my first post on Svelte 5, you might recall that you can’t directly return reactive state from a function, since the state will be read and unwrapped right at the call-site, and won’t be reactive any longer. Normally you’d have to do this:

return {
  get value() {
    return result;
  },
  set value(newData: T[]) {
    result = newData;
  },
};

Why wasn’t that needed here? It’s true, the $state() value will be read at the function’s call site. So with…

let tasks = shallowObservable(getTasks());

…the tasks variable will not be reactive. But the array itself will still be fully reactive. We can still call push, pop, splice and so on. If you can live without needing to re-assign to the variable, this is much simpler. But even if you do need to set the tasks variable to a fresh array of values, you still don’t even need to use variable assignment. Stay tuned.

I changed the initial tasks array to help out in a minute, but the rest is what you’d expect.

const getTasks = () => [
  { id: 1, title: "Task A", assigned: "Adam", importance: "Low" },
  <em>// ...</em>
  { id: 12, title: "Task L", assigned: "Adam", importance: "High" },
];

let tasks = shallowObservable(getTasks());

And with that, rendering should now work, and none of our properties are reactive. Clicking the edit buttons do nothing.

But we can now add a button to push a new task onto our array.

<button
  onclick={() =>
    tasks.value.push(
      new NonReactiveObjectGenerator({
        id: nextId++,
        title: 'New task',
        assigned: 'Adam',
        importance: 'Low'
      }) as Task
    )}
>
  Add new task
</button>

We can even add a delete button to each row.

<button onclick={() => tasks.value.splice(idx, 1)}>
  Delete
</button>

Yes, Svelte’s reactive array is smart enough to understand push and splice.

Editing tasks this way

You might be wondering if we can still actually edit the individual tasks. We assumed the tasks would be read-only, but what if that changes? We’ve been modifying the array and watching Svelte re-render correctly. Can’t we edit an individual task by just cloning the task, updating it, and then re-assigning to that index? The answer is yes, with a tiny caveat.

Overriding an array index (with a new object instance) does work, and makes Svelte update. But we can’t just do this:

tasks[idx] = { ...t, importance: "X" + t };

Since that would make the new object, which is an object literal, deeply reactive. We have to keep using our class. This time, to keep the typings simple, and to keep the code smell that is the NonReactiveObjectGenerator class hidden as much as possible, I wrote up a helper function.

function cloneNonReactive<T>(data: T): T {
  return new NonReactiveObjectGenerator(data) as T;
}

As before, the type assertion is unfortunately needed. This same function could also be used for the add function we saw above, if you prefer.

To prove editing works, we’ll leave the entire template alone, except for the importance field, which we’ll modify like so

  <div>
    <span>{t.importance + getCounter()}</span>
    <button
      onclick={() => {
        const taskClone = cloneNonReactive(t);
        taskClone.importance += 'X';
        tasks[idx] = cloneNonReactive(taskClone);
      }}
    >
      Update importance
    </button>
</div>

Now running shows everything as it’s always been.

If we click the button to change the id, title or assigned value, nothing changes, because we’re still mutating those properties directly (since I didn’t change anything) in order to demonstrate that they’re not reactive. But clicking the button to update the importance field runs the code above, and updates the entire row, showing any other changes we’ve made.

Here I clicked the button to update the title, twice, and then clicked the button to update the importance. The former did nothing, but the latter updated the component to show all changes.

Re-assigning to the tasks array

We saved a bit of convenience by returning our state value directly from our shallowObservable helper, but at the expense of not being able to assign directly to our array. Or did we?

If you know a bit of JavaScript, you might know…

tasks.length = 0;

…is the old school way to clear an array. That works with Svelte; the Proxy object Svelte sets up to make our array observable works with that. Similarly, we can set the array to a fully new array of values (after clearing it like we just saw) like this:

tasks.push(...newArray);

It’s up to you which approach you take, but hopefully Svelte ships a $state.shallow to provide the best of both worlds: the array would be reactive, and so would the binding, since we don’t have to pass it across a function boundary; it would be built directly into $state.

SvelteKit

Let’s wrap up by briefly talking about how data from SvelteKit loaders is treated in terms of reactivity. In short, it’s exactly how you’d expect. First and foremost, if you return a raw array of objects from your loader like this:

export const load = () => {
  return {
    tasks: [
      { id: 1, title: "Task A", assigned: "Adam", importance: "Low" },
      <em>// ...</em>
      { id: 12, title: "Task L", assigned: "Adam", importance: "High" },
    ],
  };
};

Then none of that data will be reactive in your component. This is to be expected. To make data reactive, you need to wrap it in $state(). As of now, you can’t call $state in a loader, only in a universal Svelte file (something that ends in .svelte.ts). Hopefully in the future Svelte will allow us to have loaders named +page.svelte.ts but for now we can throw something like this in a reactive-utils.svelte.ts file.

export const makeReactive = <T>(arg: T[]): T[] => {
  let result = $state(arg);
  return result;
};

Then import it and use it in our loader.

import { makeReactive } from "./reactive-utils.svelte";

export const load = () => {
  return {
    tasks: makeReactive([
      { id: 1, title: "Task A", assigned: "Adam", importance: "Low" },
      <em>// ...</em>
      { id: 12, title: "Task L", assigned: "Adam", importance: "High" },
    ]),
  };
};

Now those objects will support the same fine-grained reactivity we saw before. To customize which properties are reactive, you’d swap in class instances, instead of vanilla object literals, again just like we saw. All the same rules apply.

If you’re wondering why we did this…

export const makeReactive = <T>(arg: T[]): T[] => {
  let result = $state(arg);
  return result;
};

…rather than this…

export const makeReactive = <T>(arg: T[]): T[] => {
  return $state(arg);
};

… the answer is that the latter is simply disallowed. Svelte forces you to only put $state() calls into assignments. It cannot appear as a return value like this. The reason is that while returning $state variables directly across a function boundary works fine for objects and arrays, doing this for primitive values (strings or numbers) would produce a senseless result. The variable could not be re-assigned (same as we saw with the array), but as a primitive, there’d be no other way to edit it. It would just be a non-reactive constant.

Svelte forcing you to take that extra step, and assign $state to a variable before returning, is intended to help prevent you from making that mistake.

Wrapping up

One of the most exciting features of Svelte 5 is the fine-grained reactivity it adds. Svelte was already lightweight, and faster than most, if not all of the alternatives. These additions in version 5 only improve on that. When added to the state management improvements we’ve already covered in prior posts, Svelte 5 really becomes a serious framework option.

Consider it for your next project.

Article Series

]]>
https://frontendmasters.com/blog/fine-grained-reactivity-in-svelte-5/feed/ 0 3438
Relative Color Syntax — Basic Use Cases https://frontendmasters.com/blog/relative-color-syntax-basic-use-cases/ https://frontendmasters.com/blog/relative-color-syntax-basic-use-cases/#comments Mon, 12 Aug 2024 18:38:35 +0000 https://frontendmasters.com/blog/?p=3448 As of last month, Firefox 128’s support of the relative color syntax means we’ve now got support across the board. I’m excited about that as it’s an extremely powerful way to manipulate colors in CSS. Plus it was part of Interop this year so that is further proof that is trucking along nicely.

The syntax with generic names looks like this:

color-function(from origin-color channel1 channel2 channel3 / alpha)

Here’s how it works in my head:

Add Opacity to a Color you Already Have

It’s common to have CSS custom properties set up for colors on a project.

html {
  --color-yellow: oklch(80% 0.15 94);
  --color-green:  oklch(70% 0.25 140);

  ...
}

Now you want to use that yellow, but at about 50% opacity. How do you do that? There are actually a couple of ways to add transparency to an existing color, but in my opinion the relative color syntax is the nicest.

In the past, I’ve split out the color values like this:

html {
  --color-yellow-lch: 80% 0.15 94;
  --color-yellow: oklch(var(--color-yellow-lch);

  ...
}

That way I could either use the color all together, or use the split out values to apply opacity:

.box {
  background: var(--color-yellow);
  border-color: oklch(var(--color-yellow-lch) / 50%);
}

But that can get out of hand! You could also split each color into L, C, and H, the combine those, giving you five variables for every color. Too much.

With the relative color syntax, breaking down colors isn’t necessary. You apply alpha (and other transformations) on demand, leaving the original single color as the only variable (token) you need.

.box {
  background: var(--color-yellow);
  border-color: oklch(from var(--color-yellow) l c h / 50%); 
}

I much prefer the idea of keeping the main colors tokenized as custom properties, then tweaking them as needed on demand.

Darken a Color you Already Have

In the above example, we had --color-yellow and I ended by saying I prefer doing one-off tweaks on demand rather than making a whole new variable. If you have a ton of usage of a slightly-darker version of a color, then sure, make a new variable and stay consistent. But if it’s more of a one-off, relative color syntax is awesome:

.box {
  background: var(--gray-5);

  h2 {
    color: var(--color-yellow);
    /* Darkened version of yellow */
    border-bottom: 2px solid oklch(from var(--color-yellow) calc(l - 0.4) c h);
  }
}

Lighten a Color you Already Have

Same deal here. I’m using OKLCH because I like it, particularly the “uniform brightness” characteristic. Meaning when doing this darkening and lightening across different colors, it feels like it lightens/darkens the same amount. Which feels weird to write, but it’s true. Other color spaces do not lighten and darken consistently.

.box {
  background: var(--gray-5);

  h2 {
    color: var(--color-orange);
    /* Darkened version of orange */
    border-bottom: 2px solid oklch(from var(--color-orange) calc(l + 0.4) c h);
  }
}

Easy Variations

Avoiding making too many variables is a nice consequence of the relative color syntax, but you can still use the relative color syntax to make variables if it’s useful to have them.

I like the idea of starting with a base color, perhaps a slightly tinted gray, and then making the official variations with the relative color syntax.

html {
  --base-gray: oklch(12.94% 0.02 159);
  
  --gray-1: var(--base-gray);
  --gray-2: oklch(from var(--base-gray) calc(l + 0.1) c h);
  --gray-3: oklch(from var(--base-gray) calc(l + 0.2) c h);
  --gray-4: oklch(from var(--base-gray) calc(l + 0.3) c h);
  --gray-5: oklch(from var(--base-gray) calc(l + 0.4) c h);
  --gray-6: oklch(from var(--base-gray) calc(l + 0.5) c h);
  --gray-7: oklch(from var(--base-gray) calc(l + 0.6) c h);
  --gray-8: oklch(from var(--base-gray) calc(l + 0.7) c h);
}

The fact that you can start with any color, use any color function, and manipulate any part of the color is incredibly powerful. The above use cases are pretty basic. I’m sure more talented designers or developers who deeply know color will be able to do much more interesting things!

]]>
https://frontendmasters.com/blog/relative-color-syntax-basic-use-cases/feed/ 1 3448
Exploring the Possibilities of Native JavaScript Decorators https://frontendmasters.com/blog/exploring-the-possibilities-of-native-javascript-decorators/ https://frontendmasters.com/blog/exploring-the-possibilities-of-native-javascript-decorators/#comments Fri, 09 Aug 2024 18:54:47 +0000 https://frontendmasters.com/blog/?p=3381 We’ve known it for a while now, but JavaScript is eventually getting native support for decorators. The proposal is in stage 3 — it’s inevitable! I’m just coming around to explore the feature, and I’m kinda kicking myself for waiting so long, because I’m finding it to be tremendously helpful. Let’s spend some time exploring it.

The Pattern vs The Feature

It’s probably worth clarifying what’s meant by a “decorator.” Most of the time, people are talking about one of two things:

The decorator design pattern

This is the higher-level concept of augmenting or extending a function’s behavior by “decorating” it. Logging is a common example. You might want to know when and with what parameters it’s called, so you wrap it with another function:

function add(a, b) {
  return a + b;
}

function log(func) {
  return function (...args) {
    console.log(
      `method: ${func.name} | `,
      `arguments: ${[...args].join(", ")}`
    );
    return func.call(this, ...args);
  };
}

const addWithLogging = log(add);

addWithLogging(1, 2);
// adding 1 2

There’s no new language-specific feature here. One function simply accepts another as an argument and returns a new, souped-up version. The original function has been decorated.

Decorators as a feature of the language

The decorator feature is a more tangible manifestation of the pattern. It’s possible you’ve seen an older, unofficial version of this before. We’ll keep using the logging example from above, but we’ll first need to refactor a bit because language-level decorators can only be used on class methods, fields, and on classes themselves.

// The "old" decorator API:

function log(target, key, descriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args) {
    console.log(
      `method: ${originalMethod.name} | `,
      `arguments: ${[...args].join(", ")}`
    );

    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class Calculator {
  @log // <-- Decorator applied here.
  add(a, b) {
    return a + b;
  }
}

new Calculator().add(1, 2); // method: add | arguments: 1, 2

Despite being non-standard, there are a number of popular, mature libraries out there that have used this implementation. TypeORMAngular, and NestJS are just a few of the big ones. And I’m glad they have. It’s made building applications with them feel cleaner, more expressive, and easier to maintain.

But because it’s non-standard, it could become problematic. For example, there’s some nuance between how it’s implemented by Babel and TypeScript, which probably caused frustration for engineers moving between applications with different build tooling. Standardization would serve them well.

The Slightly Different Official API

Fortunately, both TypeScript (as of v5) and Babel (via plugin) now support the TC39 version of the API, which is even simpler:

function log(func, context) {
  return function (...args) {
    console.log(
      `method: ${func.name} | `,
      `arguments: ${[...args].join(", ")}`
    );

    func.call(this, ...args);
  };
}

class Calculator {
  @log
  add(a, b) {
    return a + b;
  }
}

new Calculator().add(1, 2); // method: add | arguments: 1, 2

As you can see, there’s much less of a learning curve, and it’s fully interchangeable with many functions that have been used as decorators until now. The only difference is that it’s implemented with new syntax.

Exploring the Use Cases

There’s no shortage of scenarios in which this feature will be handy, but let’s try out a couple that come to mind.

Debouncing & Throttling

Limiting the number of times an action occurs in a given amount of time is an age-old need on the web. Typically, that’s meant reaching for a Lodash utility or rolling an implementation yourself.

Think of a live search box. To prevent user experience issues and network load, you want to debounce those searches, only firing a request when the user has stopped typing for a period of time:

function debounce(func) {
  let timeout = null;

  return function (...args) {
    clearTimeout(timeout);

    timeout = setTimeout(() => {
      func.apply(this, args);
    }, 500);
  };
}

const debouncedSearch = debounce(search);

document.addEventListener('keyup', function(e) {
  // Will only fire after typing has stopped for 500ms.
  debouncedSearch(e.target.value);
});  

But decorators can only be used on a class or its members, so let’s flesh out a better example. You’ve got a ViewController class with a method for handling keyup events:

class ViewController {
  async handleSearch(query) {
    const results = await search(query);

    console.log(`Update UI with:`, results);
  }
}

const controller = new ViewController();

input.addEventListener('keyup', function (e) {
  controller.handleSearch(e.target.value);
});

Using the debounce() method we wrote above, implementation would be clunky. Focusing in on the ViewController class itself:

class ViewController {
  handleSearch = debounce(async function (query) {
    const results = await search(query);

    console.log(`Got results!`, results);
  });
}

You not only need to wrap your entire method, but you also need to switch from defining a class method to an instance property set to the debounced version of that method. It’s a little invasive.

Updating to a Native Decorator

Turning that debounce() function into an official decorator won’t take much. In fact, the way it’s already written fits the API perfectly: it accepts the original function and spits out the augmented version. So, all we need to do is apply it with the @ syntax:

class ViewController {
  @debounce
  async handleSearch(query) {
    const results = await search(query);

    console.log(`Got results!`, results);
  }
}

That’s all it takes — a single line — for the exact same result.

We can also make the debouncing delay configurable by making debounce() accept a delay value and return a decorator itself:

// Accept a delay:
function debounce(delay) {
  let timeout = null;

  // Return the configurable decorator:
  return function (value) {
    return function (...args) {
      clearTimeout(timeout);

      timeout = setTimeout(() => {
        value.call(this, ...args);
      }, delay);
    };
  };
}

Using it just means calling our decorator wrapper as a function and passing the value:

class ViewController {
  @debounce(500)
  async handleSearch(query) {
    const results = await search(query);

    console.log(`Got results!`, results);
  }
}

That’s a lot of value for minimal code wrangling, especially support being provided by TypeScript and Babel — tools already well-integrated in our build processes.

Memoization

Whenever I think of great memoization that’s syntactically beautiful, Ruby first comes to mind. I’ve written about how elegant it is in the past; the ||= operator is all you really need:

def results
  @results ||= calculate_results
end

But with decorators, JavaScript’s making solid strides. Here’s a simple implementation that caches the result of a method, and uses that value for any future invocations:

function memoize(func) {
  let cachedValue;

  return function (...args) {
    // If it's been run before, return from cache.
    if (cachedValue) {
      return cachedValue;
    }

    cachedValue = func.call(this, ...args);

    return cachedValue;
  };
}

The nice thing about this is that each invocation of a decorator declares its own scope, meaning you can reuse it without risk of the cachedValue being overwritten with an unexpected value.

class Student {
  @memoize
  calculateGPA() {
    // Expensive computation...
    return 3.9;
  }

  @memoize
  calculateACT() {
    // Expensive computation...
    return 34;
  }
}

const bart = new Student();

bart.calculateGPA();
console.log(bart.calculateGPA()); // from cache: 3.9

bart.calculateACT();
console.log(bart.calculateACT()); // from cache: 34

Going further, we could also memoize based on the parameters passed to a method:

function memoize(func) {
  // A place for each distinct set of parameters.
  let cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);

    // This set of parameters has a cached value.
    if (cache.has(key)) {
      return cache.get(key);
    }

    const value = func.call(this, ...args);

    cache.set(key, value);

    return value;
  };
}

Now, regardless of parameter usage, memoization can become even more flexible:

class Student {
  @memoize
  calculateRank(otherGPAs) {
    const sorted = [...otherGPAs].sort().reverse();

    for (let i = 0; i <= sorted.length; i++) {
      if (this.calculateGPA() > sorted[i]) {
        return i + 1;
      }
    }

    return 1;
  }

  @memoize
  calculateGPA() {
    // Expensive computation...
    return 3.4;
  }
}

const bart = new Student();

bart.calculateRank([3.5, 3.7, 3.1]); // fresh
bart.calculateRank([3.5, 3.7, 3.1]); // cached
bart.calculateRank([3.5]); // fresh

That’s cool, but it’s also worth noting that you could run into issues if you’re dealing with parameters that can’t be serialized (undefined, objects with circular references, etc.). So, use it with some caution.

Memoizing Getters

Since decorators can be used on more than just methods, a slight adjustment means we can memoize getters too. We just need to use context.name (the name of the getter) as the cache key:

function memoize(func, context) {
  let cache = new Map();

  return function () {
    if (cache.has(context.name)) {
      return cache.get(context.name);
    }

    const value = func.call(this);

    cache.set(context.name, value);

    return value;
  };
}

Implementation would look the same:

class Student {
  @memoize
  get gpa() {
    // Expensive computation...
    return 4.0;
  }
}

const milton = new Student();

milton.gpa // fresh
milton.gpa // from the cache

That context object contains some useful bits of information, by the way. One of those is the “kind” of field being decorated. That means we could even take this a step further by memoizing the getters and methods with the same decorator:

function memoize(func, context) {
  const cache = new Map();

  return function (...args) {
    const { kind, name } = context;

    // Use different cache key based on "kind."
    const cacheKey = kind === 'getter' ? name : JSON.stringify(args);

    if (cache.has(cacheKey)) {
      return cache.get(cacheKey);
    }

    const value = func.call(this, ...args);

    cache.set(cacheKey, value);

    return value;
  };
}

You could take this much further, but we’ll draw the line there for now, and instead shift to something a little more complex.

Dependency Injection

If you’ve worked with a framework like Laravel or Spring Boot, you’re familiar with dependency injection and the “inversion of control (IoC) container” for an application. It’s a useful feature, enabling you to write components more loosely coupled and easily testable. With native decorators, it’s possible to bring that core concept to vanilla JavaScript as well. No framework needed.

Let’s say we’re building an application needing to send messages to various third-parties. Triggering an email, sending an analytics event, firing a push notification, etc. Each of these are abstracted into their own service classes:

class EmailService {
  constructor() {
    this.emailKey = process.env.EMAIL_KEY;
  }
}

class AnalyticsService {
  constructor(analyticsKey) {
    this.analyticsKey = analyticsKey;
  }
}

class PushNotificationService {
  constructor() {
    this.pushNotificationKey = process.env.PUSH_NOTIFICATION_KEY;
  }
}

Without decorators, it’s not difficult to instantiate those yourself. It might look something like this:

class MyApp {
  constructor(
    emailService = new EmailService(),
    analyticsService = new AnalyticsService(),
    pushNotificationService = new PushNotificationService()
  ) {
    this.emailService = emailService;
    this.analyticsService = analyticsService;
    this.pushNotificationService = pushNotificationService;

    // Do stuff...
  }
}

const app = new MyApp();

But now you’ve cluttered your constructor with parameters that’ll never otherwise be used during runtime, and you’re taking on full responsibility for instantiating those classes. There are workable solutions out there (like relying on separate modules to create singletons), but it’s not ergonomically great. And as complexity grows, this approach will become more cumbersome, especially as you attempt to maintain testability and stick to good inversion of control.

Dependency Injection with Decorators

Now, let’s create a basic dependency injection mechanism with decorators. It’ll be in charge of registering dependencies, instantiating them when necessary, and storing references to them in a centralized container.

In a separate file (container.js), we’ll build a simple decorator used to register any classes we want to make available to the container.

const registry = new Map();

export function register(args = []) {
  return function (clazz) {
    registry.set(clazz, args);
  };
}

There’s not much to it. We’re accepting the class itself and optional constructor arguments needed to spin it up. Next up, we’ll create a container to hold the instances we create, as well as an inject() decorator.

const container = new Map();

export function inject(clazz) {
  return function (_value, context) {
    context.addInitializer(function () {
      let instance = container.get(clazz);

      if (!instance) {
        instance = Reflect.construct(clazz, registry.get(clazz));
        container.set(clazz, instance);
      }

      this[context.name] = instance;
    });
  };
}

You’ll notice we’re using something else from the decorator specification. The addInitializer() method will fire a callback only after the decorated property has been defined. That means we’ll be able to lazily instantiate our injected dependencies, rather than booting up every registered class all at once. It’s a slight performance benefit. If a class uses the EmailService for example, but it’s never actually instantiated, we won’t unnecessarily boot up an instance of EmailService either.

That said, here’s what’s going on when the decorator is invoked:

  • We check for any active instance of the class in our container.
  • If we don’t have one, we create one using the arguments stored in the registry, and store it in the container.
  • That instance is assigned to the name of the field we’ve decorated.

Our application can now handle dependencies a little more elegantly.

import { register, inject } from "./container";

@register()
class EmailService {
  constructor() {
    this.emailKey = process.env.EMAIL_KEY;
  }
}
@register()
class AnalyticsService {
  constructor(analyticsKey) {
    this.analyticsKey = analyticsKey;
  }
}
@register()
class PushNotificationService {
  constructor() {
    this.pushNotificationKey = process.env.PUSH_NOTIFICATION_KEY;
  }
}

class MyApp {
  @inject(EmailService)
  emailService;

  @inject(AnalyticsService)
  analyticsService;

  @inject(PushNotificationService)
  pushNotificationService;

  constructor() {
    // Do stuff.
  }
}

const app = new MyApp();

And as an added benefit, it’s straightforward to substitute those classes for mock versions of them as well. Rather than overriding class properties, we can less invasively inject our own mock classes into the container before the class we’re testing is instantiated:

import { vi, it } from 'vitest';
import { container } from './container';
import { MyApp, EmailService } from './main';

it('does something', () => {
  const mockInstance = vi.fn();
  container.set(EmailService, mockInstance);

  const instance = new MyApp();
  
  // Test stuff.
});

That makes for less responsibility on us, tidy inversion of control, and straightforward testability. All made easy by a native feature.

Just Scratching the Surface

If you read through the proposal, you’ll see that the decorator specification is far deeper than what’s been explored here, and will certainly open up some novel use cases in the future, especially once more runtimes support it. But you don’t need to master the depths of the feature in order to benefit. At its foundation, the decorator feature is still firmly seated on the decorator pattern. If you keep that in mind, you’ll be in a strong position to greatly benefit from it in your own code.

]]>
https://frontendmasters.com/blog/exploring-the-possibilities-of-native-javascript-decorators/feed/ 1 3381
It’s Time To Talk About “CSS5” https://frontendmasters.com/blog/its-time-to-talk-about-css5/ https://frontendmasters.com/blog/its-time-to-talk-about-css5/#respond Thu, 08 Aug 2024 14:04:03 +0000 https://frontendmasters.com/blog/?p=3397 Brecht De Ruyte has a good rundown of what’s up with future named versions of CSS. You might remember “CSS3” and how hot of a buzzword that is. JavaScript still has successful “ES202X” naming that groups features into useful buckets. But CSS hasn’t benefited from named groups since CSS3.

I’ve written:

CSS3, and perhaps to a larger degree, “HTML5”, became (almost) household names. It was so successful, it’s leaving us wanting to pull that lever again. It was successful on a ton of levels:

  • It pushed browser technology forward, particularly on technologies that had been stale for too long.
  • It got site owners to think, “hey maybe it’s a good time to update our website.
  • It got educators to think, “hey maybe it’s a good time to update our curriculums.

Then it felt like a pipedream. Now it’s real! You can see how the official community group is grouping CSS features and comment on the RFC (Request for Comments). I take no issues with the groups. Ship it.

]]>
https://frontendmasters.com/blog/its-time-to-talk-about-css5/feed/ 0 3397
Snippets in Svelte 5 https://frontendmasters.com/blog/snippets-in-svelte-5/ https://frontendmasters.com/blog/snippets-in-svelte-5/#respond Wed, 07 Aug 2024 13:03:50 +0000 https://frontendmasters.com/blog/?p=3341 This post is the second in a 3-part series on Svelte 5. Part one was a basic introduction, covering nuts and bolts features like state, props, and effects. This post is all about snippets, an exciting new feature that allows for content reuse, and more importantly, injecting content into components you render.

Article Series

If you’d like to see and experiment with the code in this post, see the GitHub repo.

What are snippets?

Snippets are a new feature in Svelte 5. They allow you to define, well… snippets of content. They’re almost lightweight components that you can find inside of a component file. Before you get too excited: they do not, as of now, allow you to define multiple components in one file. Snippets cannot be exported from anywhere, and even if they could, they do not allow you to define state. They are limited to props.

They seem initially similar to React’s minimally useful Stateless Functional Components from back before hooks were a thing. But snippets also have a second use: they allow you to inject content into other components, and in so doing replace one of Svelte’s most awkward features: slots.

Let’s see how.

Defining snippets

We define snippets with the #snippet directive. The simplest snippet imaginable looks like this:

{#snippet helloWorld()}
  <span>Hello World</span>
{/snippet}

That defines the snippet. To render the snippet, we use the @render directive, like this:

{@render helloWorld()}

As you might have guessed, snippets can also receive props, or really, parameters, since snippets are more of a function, than a component. Parameters are listed in the parens, with types if you’re using TypeScript.

{#snippet productDisplay(p: Product)}
<div>
  <img src="{p.url}" alt="product url" />
  <div>
    <h2>{p.name}</h2>
    <span>${p.price.toFixed(2)}</span>
  </div>
</div>
{/snippet}

Snippets can render other snippets

For example, this simple snippet…

{#snippet productReview(review: Review)}
<div>
  <span>{review.date}</span>
  <span>{review.content}</span>
</div>
{/snippet}

… can be used in this bigger snippet:

{#snippet productDisplay(p: Product)}
<div>
  <div>
    <img src="{p.url}" alt="product url">
    <div>
      <h2>{p.name}</h2>
      <span>${p.price.toFixed(2)}</span>
    </div>
  </div>
  <h3>Reviews:</h3>
  <div>
    {#each p.reviews ?? [] as review}
      {@render productReview(review)}
    {/each}
  </div>
</div>
{/snippet}

Then you can reuse that productDisplay snippet with different products in your component. Let’s see a minimal, full example:

<script lang="ts">
  type Review = {
    date: string;
    content: string;
  };
  type Product = {
    name: string;
    url: string;
    price: number;
    reviews?: Review[];
  };

  let searchedBook = $state<Product>({
    name: "Effective TypeScript: 83 Specific Ways to Improve Your TypeScript, 2nd Edition",
    url: "https://m.media-amazon.com/images/I/71eWL4AqPqL._SL1500_.jpg",
    price: 44.99,
    reviews: [
      { date: "2/14/2024", content: "Absolutely loved this book" },
      { date: "6/2/2024", content: "Even better than the first edition" },
    ],
  });
  let relatedProduct = $state<Product>({
    name: "Modern C++ Design: Generic Programming and Design Patterns Applied",
    url: "https://m.media-amazon.com/images/I/914ncVx1hxL._SL1413_.jpg",
    price: 55.49,
  });
</script>

{#snippet productReview(review: Review)}
<div>
  <span>{review.date}</span>
  <span>{review.content}</span>
</div>
{/snippet}

{#snippet productDisplay(p: Product)}
<div>
  <div>
    <img src="{p.url}" alt="product url" />
    <div>
      <h2>{p.name}</h2>
      <span>${p.price.toFixed(2)}</span>
    </div>
  </div>
  <h3>Reviews:</h3>
  <div>{#each p.reviews ?? [] as review} {@render productReview(review)} {/each}</div>
</div>
{/snippet}

<section>
  <h1>Product Display Page</h1>

  {@render productDisplay(searchedBook)}

  <aside>You might also be interested in:</aside>

  {@render productDisplay(relatedProduct)}
</section>

If that was the extent of Snippets they’d be a marginally useful convenience for re-using small bits of markup within a single component.

But the main benefit of snippets is for injecting content into components. Previously, if you wanted to pass content into a component you’d use slots. Slots were always an awkward feature of Svelte, but they’re now deprecated in Svelte 5. We won’t cover them here, so check out the docs if you’re curious.

Passing snippets to components

Snippets shine brightest when we pass them into other components. Let’s imagine a (grossly simplified) DisplayProduct page. It takes in a product, an optional related product, and a snippet to display a single product. This component will also render content in the header, which we’ll also pass in as a snippet.

<script lang="ts">
  import type { Snippet } from "svelte";
  import type { Product } from "./types";

  type Props = {
    product: Product;
    relatedProduct?: Product;
    productDisplay: Snippet<[Product]>;
    children: Snippet;
  };

  let { product, relatedProduct, productDisplay, children }: Props = $props();
</script>

<section>
  {@render children()}
  {@render productDisplay(product)}
  
  {#if relatedProduct}
    <aside>You might also be interested in:</aside>
    {@render productDisplay(relatedProduct)}
  {/if}
</section>

There’s a Snippet type that Svelte exports for us, so we can type the snippets we’re receiving. Specifying the parameters that a snippet receives is a little weird, because of how TypeScript is: we list the argumentes as a Tuple. So our productDisplay snippet will take a single argument that’s a Product.

The snippet for showing the header I decided to name “children” which has some significance as we’ll see in a moment.

Let’s put this component to use:

{#snippet productDisplay(p: Product)}
<div>
  <img src="{p.url}" alt="Image of product">
  <div>
    <h2>{p.name}</h2>
    <span>${p.price.toFixed(2)}</span>
  </div>
</div>
{/snippet}

<DisplayProduct product="{searchedBook}" relatedProduct="{recommendedBook}" {productDisplay}>
  <h1>Product Display Page</h1>
</DisplayProduct>

We’re passing the productDisplay snippet in for the productDisplay prop. Little note: Svelte allows you to write {a} instead of a={a} as a convenient shortcut.

But notice the content we put directly inside of the DisplayProduct tags. If the component has a prop called children that’s a snippet, this content will be passed as that snippet. This is a special case just for props called children (similar to the children prop in React). You don’t have to do this; you’re free to manually pass a children prop, just like we did for productDisplay if you really want to.

Let’s take a look at one more authoring convenience Svelte 5 gives us. If we’re just defining a snippet to be passed one time, to one component, Svelte lets us clean the syntax up a bit, like so:

<DisplayProduct product="{searchedBook}" relatedProduct="{recommendedBook}">
  <h1>Product Display Page</h1>
  {#snippet productDisplay(p: Product)}
  <div>
    <img src="{p.url}" alt="product url" />
    <div>
      <h2>{p.name}</h2>
      <span>${p.price.toFixed(2)}</span>
    </div>
  </div>
  {/snippet}
</DisplayProduct>

As before, we have our <h1> content directly inside of the tags, as children. But we’ve also defined a snippet inside of those tags. This is a nice shorthand for passing a snippet as a prop (with the same name) to our component. Don’t worry, if the name you give this inline snippet doesn’t match a prop, TypeScript will tell you.

Default Content with Snippets

One nice feature with slots is that you could define default content pretty easily.

<slot name="header-content">
  <span>Default content</span>
</slot>

Snippets don’t quite have anything like this built in, but they’re a flexible enough primitive that you really don’t need it.

Let’s see how we can provide our own default content for when a Snippet is not passed in. As before let’s say we have our DisplayProduct component, except now our productDisplay and children snippets are optional

type Props = {
  product: Product;
  relatedProduct?: Product;
  productDisplay?: Snippet<[Product]>;
  children?: Snippet;
};

let { product, relatedProduct, productDisplay, children }: Props = $props();

We have a few straightforward options for falling back to our own default content. We can simply test if we have a value for the snippet right in our template, and render the fallback if not.

{#if children}
  {@render children()} 
{:else}
  <h1>Fallback content</h1>
{/if}

Or, we can set up our fallback right in our script:

let productDisplaySnippetToUse: Snippet<[Product]> = productDisplay ?? productDisplayFallback;
{#snippet productDisplayFallback(p: Product)}
<div>
  <img src="{p.url}" alt="product url" />
  <div>
    <h2>{p.name}</h2>
  </div>
</div>
{/snippet}

Then we render that:

{@render productDisplaySnippetToUse(product)}

Parting thoughts

Svelte 5 is an exciting release. This post turned to one of the more interesting new features: snippets, useful for injecting content into components, and for re-using small bits of content within a single component.

Out with slots, in with snippets.

Article Series

]]>
https://frontendmasters.com/blog/snippets-in-svelte-5/feed/ 0 3341
Scaling One Million Checkboxes https://frontendmasters.com/blog/scaling-one-million-checkboxes/ https://frontendmasters.com/blog/scaling-one-million-checkboxes/#respond Tue, 06 Aug 2024 14:06:37 +0000 https://frontendmasters.com/blog/?p=3386 Nolen Royalty made a website (now turned off) with a million checkboxes on it which was realtime for everyone viewing it. Users can check (or uncheck) a checkbox and that is persisted to a datastore and propagated to all visitors instantly. This, as it turns out, is a non-trivial task if you want it to run well, handle scale, and not cost a ton of money.

I’m posting this because if reading this kind of thing is interesting and kind of thrilling for you, a career in DevOps is probably not a bad idea for you.

]]>
https://frontendmasters.com/blog/scaling-one-million-checkboxes/feed/ 0 3386
Figma Typography Variables https://frontendmasters.com/blog/figma-typography-variables/ https://frontendmasters.com/blog/figma-typography-variables/#respond Mon, 05 Aug 2024 14:07:03 +0000 https://frontendmasters.com/blog/?p=3203 When we were recording the “Figma for Developers, v2” workshop, I mentioned that Variables were in beta and that they couldn’t be used with typography yet. Coincidentally, typography support was added the next day after the recording. Figma variables are also no longer in beta.

Let the record show, I was correct during the recording—and that’s the important part, right? Let’s review what Figma Variables are, then explore how to use them with your typography system.

A Brief Review of Variables in Figma

In Figma, Variables are placeholders for reusable values—just like JavaScript. These values include colors, numeric values, and more.

Variable dropdown includes Color, Number, String, and Boolean
The variable types in Figma

Once set, you can use them across different design elements in your Figma designs. Variables can also be shared with your team and used across multiple Figma files. This makes it easy to make sweeping changes to your design system with a few button clicks.

Using Variables to Control Typography in Figma

Previously, Figma’s variables were great for colors and spacing, ensuring consistency. However, we lacked the same flexibility with fonts. We had to use various styles and components for different themes or brands. Styles are still incredibly useful, but they lack the ability to change a single value used by multiple styles. This makes a task like switching a font incredibly tedious as you’d have to update every single style. In the course, we explored some approaches using various plugins in an attempt to automate this process, but the constraint was still there. This was a significant limitation and meant that we had to combine multiple approaches in order to maintain consistency throughout our design system.

Now that variables can be used with typography, you can now define variables for font settings. Figma now supports using variables for the following properties: 

  • font family
  • font weight & style
  • font size
  • line height
  • letter spacing
  • paragraph spacing & indentation

Each of these properties can be controlled using the same variable types that we saw when we walked through variables during the course, with numbers and strings being the most pertinent for defining typography properties. For example, you can use a string variable to define the font family.

With the values set, you can select a variable when selecting the font family in the left-side panel in the Figma canvas. Changing the variable will now change all text in your designs that reference this variable, allowing you to quickly make changes across your entire design by updating a single value.

Here’s a quick video demonstrating setting a block of text to a variable, then changing that variable:

There are still a few limitations. For example, variables don’t support percentages for line heights. Variables also don’t autocomplete for spacing or line height. Hopefully, Figma will address these soon.

Using Variables with Modes

In the course, we used Variable Modes to switch between light and dark themes as well as different responsive breakpoints. This approach also works with our new typography variables allowing your variables to point to different variables depending on what mode you’re in.

Imagine managing font sizes for different viewports. Create a “Typography” collection in the variables editor. We’ll create two modes: Desktop and Mobile. Depending on which mode we’re using the variables will have one of two values as seen in the screenshot below.

With these changes, we can switch to a smaller set of font sizes when designing for mobile. If we need to change the font size across all of our mobile views, we can simply update the appropriate variable. 

One thing that  makes variable modes powerful is that any layer set to Auto will inherit the any mode set on the parent. This means that if you switch the mode of the frame containing multiple text elements from Desktop to Mobile, all of your typography will automatically update to the values defined in the Mobile mode.

Being able to support different font sizes and spacing across viewports is the most compelling application of typography variables for me, personally. But, they could also be useful if you had to support multiple brands or themes in your design system. 

For example, you could have a different mode for each brand theme you support. You could also define different font families for your Android application as opposed to your iOS application. You could even define different font sizes for your marketing website as opposed to your application’s user interface.

What About Styles?

Prior to supporting variables within typography in Figma, we used Styles—and these are still useful. A variable can represent one value. Styles, however, can store a composite of values (e.g. font family, size, and line height). This gives us the ability to use variables as primitives, define the font, size, and spacing for our typography and then save that combination as a style.

It’s less about whether to use styles or variables and more about how to use styles and variables. What’s really cool about this is that you can use a single variable as part of a number of different types. Updating that variable will then immediately update every style that relies on that variable.

In Conclusion

Introducing variable support for typography in Figma is definitely a welcome change that we’re already beginning to leverage in our internal design system at Temporal. Variables now unifies colors, spacing, and typography using a single approach. Combining variables with styles is powerful, as well. Variables allow for individual changes, while styles provide preset options. This mix helps us build and update design systems with a lot less hassle.

That said, some features are missing, such as percentage line heights and spacing suggestions. But, I suspect those features will be coming soon and I’ll have to update this yet again.

In the meantime, I encourage you to take the typography in one of your existing designs and update using typography variables. I suspect you’ll be pleasantly surprised with how easy they are to work with.

]]>
https://frontendmasters.com/blog/figma-typography-variables/feed/ 0 3203
What if you used Container Units for… everything? https://frontendmasters.com/blog/what-if-you-used-container-units-for-everything/ https://frontendmasters.com/blog/what-if-you-used-container-units-for-everything/#respond Fri, 02 Aug 2024 16:49:11 +0000 https://frontendmasters.com/blog/?p=3108 I said to myself I said what if I used container units for every single unit in a design? I was wondering, partially because I thought the answer might be well, everything will probably scale really nicely then. Container units, in case you haven’t heard of them, are unit (like px or rem, but more closely related to viewport units like vw or vi) that are sized relatively to the container that they are in.

Turns out, surprise surprise, that it’s not that easy. There are plenty of things that container queries are awkward at or just not the right unit for.

I had a play with a pretty simple grid of cards of various sizes. Don’t consider this demo in good shape, it’s just what I used to have a plan.

Potential Problem: You Can’t Style The Element You Query

This is a fairly known thing with container queries, but the weirdness with it compounds a bit with container units, as the desire to use those units right away on a container is strong. To be clear, the container units will “work”, they’ll just be based on the next-higher-up container, which if there isn’t a declared one will be the document.

.card-wrap {
  container: cardWrap / inline-size;

  padding: 2cqi;
  border-radius: 4cqi;

  .card {
    border-radius: 4cqi;
  }
}

Above, the border-radius will be different despite looking like it will be the same, because the container the units reference are different.

See how the outer border radius’ match, but the inner card are different and look off on the larger card.

Potential Solution: Style Nothing on the Container

Be vigilant! It will save headaches if you are hardfast when you set a container you do no other size-based styling. If that means adding an extra otherwise meaningless <div> wrapper, well, that’s not ultra ideal as DOM weight does matter, but it’s probably fine.

Potential Problem: Too Small & Too Big

If you only use container units for something like font-size, it’s pretty easy to get into a situation where text, and text-based elements, end up either too big or too small.

Here the text on the larger card feels a bit too big but the tags are OK. The text on the smaller card feels OK, but those tags are too small.

Either is annoying, but too small is also an accessibility failure.

Using container units (or viewport units) alone is a bad practice for text sizing. It’s fixable though.

Potential Solution: Clamp

Making sure text doesn’t get too small or too big is solved with a CSS clamp() function. And while we’re at it, we can sprinkle in a relative unit to make sure that users font sizing preferences are honored.

You can still use container units, but set those limits and use a bit of relative mixed in.

.tag {
  /* Don't */
  font-size: 4cqi;

  /* Do */
  font-size: clamp(16px, 4cqi + 0.5rem, 24px);
}

Potential Problem: Rows vs Columns

One strong use case for a container query is shifting layout at container based breakpoints. If that container goes from wide-and-short to narrow-and-tall, then the width-based (probably the most common) container units will be super different.

Say the container here is the card itself, and you size some icons to work with that context when they are horizontal. But then if you shift the layout to a narrow column for the icons, as the card gets bigger, the sizing doesn’t work in that context now.

Bonus problem: you can’t use container units to set the grid-template-columns as they can never be based on an element that is reliably the same width as the grid.

Instead, if we make the element around the icons the container, and thus it changes width when the layout changes, the change in our icon size can be too dramatic.

Potential Solution: Use a Different Unit

And here’s the rub. You just don’t have to use container units for everything. I doubt anyone ever intended for them to be used that way. It’s just a fun exercise, especially as strong scalability is a great effect.

In this case maybe something like cqmax units would be workable, so the unit is based on the containers longest edge.

.actions {
  container: actions / inline-size;

  svg {
    display: block;
    width: 4cqmax;
    min-width: 24px;
    max-width: 100%;
    aspect-ratio: 1;
  }
}

But… nah. It’s too weird. I’d say just use relative units or pixels or something here.

In the end, if container units are helpful for a scaling effect based on the size of an element you want to achieve, go for it! The support is good. But don’t force it.

If you’re up for a challenge, have a play with it. Try something like converting every unit in a more complex layout like this into containers with container units.

]]>
https://frontendmasters.com/blog/what-if-you-used-container-units-for-everything/feed/ 0 3108
So you think you know box shadows? https://frontendmasters.com/blog/so-you-think-you-know-box-shadows/ https://frontendmasters.com/blog/so-you-think-you-know-box-shadows/#respond Thu, 01 Aug 2024 14:35:32 +0000 https://frontendmasters.com/blog/?p=3322 David Gerrells has a bunch of fun rendering far too many CSS box-shadows for things that box-shadow was never meant to do.

I found out my m1 can render a stupid number of these bad boys and so I set out to see just how far you can push them and boy did I.

Because box-shadow mimics the shape of the original element, doesn’t have to have any blur at all, and can be colored any color, they can be a way to draw anything you want wherever you want with a single DOM element. Doing faux ray-tracing as David does at the end is not something I thought I’d ever see.

I found it fun looking at the DevTools while the demos were cooking.

]]>
https://frontendmasters.com/blog/so-you-think-you-know-box-shadows/feed/ 0 3322