Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Mon, 29 Dec 2025 14:11:36 +0000 en-US hourly 1 https://wordpress.org/?v=6.9 225069128 How I Write Custom Elements with lit-html https://frontendmasters.com/blog/custom-elements-with-lit-html/ https://frontendmasters.com/blog/custom-elements-with-lit-html/#comments Mon, 29 Dec 2025 14:11:35 +0000 https://frontendmasters.com/blog/?p=8102 When I started learning more about web development, or more specifically about front-end frameworks, I thought writing components was so much better and more maintainable than calling .innerHTML() whenever you need to perform DOM operations. JSX felt like a great way to mix HTML, CSS, and JS in a single file, but I wanted a more vanilla JavaScript solution instead of having to install a JSX framework like React or Solid.

So I’ve decided to go with lit-html for writing my own components.

Why not use the entire lit package instead of just lit-html?

Honestly, I believe something like lit-html should be a part of vanilla JavaScript (maybe someday?). So by using lit-html, I basically pretend like it is already. It’s my go-to solution when I want to write HTML in JavaScript. For more solid reasons, you can refer to the following list:

  • Size difference. This often does not really matter for most projects anyway.)
    • lit-html – 7.3 kb min, 3.1 kb min + gzip
    • lit – 15.8 kb min, 5.9 kb min + gzip
  • LitElement creates a shadow DOM by default. I don’t want to use the shadow DOM when creating my own components. I prefer to allow styling solutions like Tailwind to work instead of having to rely on solutions like CSS shadow parts to style my components. The light DOM can be nice.
  • import { html, render } from "lit-html" is all you need to get started to write lit-html templates whereas Lit requires you to learn about decorators to use most of its features. Sometimes you may want to use Lit directives if you need performant renders but it’s not necessary to make lit-html work on your project.

I will be showing two examples with what I consider to be two distinct methods to create a lit-html custom element. The first example will use what I call a “stateless render” because there won’t be any state parameters passed into the lit-html template. Usually this kind of component will only call the render method once during its lifecycle since there is no state to update. The second example will use a “stateful render” which calls the render function every time a state parameter changes.

Stateless Render

For my first example, the custom-element is a <textarea> wrapper that also has a status bar similar to Notepad++ that shows the length and lines of the content inside the <textarea>. The status bar will also display the position of the cursor and span of the selection if any characters are selected. Here is a picture of what it looks like for those readers that haven’t used Notepad++ before.

A screenshot of a text editor displaying an excerpt about Lorem Ipsum, highlighting the text in yellow and showing line and character counts.

I used a library called TLN (“Textarea with Line Numbers”) to make the aesthetic of the textarea feel more like Notepad++, similar to the library’s official demo. Since the base template has no state parameters, I’m using plain old JavaScript events to manually modify the DOM in response to changes within the textarea. I also used the render function again to display the updated status bar contents instead of user .innerHTML() to keep it consistent with the surrounding code.

Using lit-html to render stateless components like these is useful, but perhaps not taking full advantage of the power of lit-html. According to the official documentation:

When you call render, lit-html only updates the parts of the template that have changed since the last render. This makes lit-html updates very fast.

You may ask: “Why should you use lit-html in examples like this where it won’t make that much of a difference performance wise? Since the root render function is really only called once (or once every connectedCallback()) in the custom elements lifecycle.”

My answer is that, yes, it’s not necessary if you just want rendering to the DOM to be fast. The main reason I use lit-html is that the syntax is so much nicer to me compared to setting HTML to raw strings. With vanilla JavaScript, you have to perform .createElement(), .append(), and .addEventListener() to create deeply nested HTML structures. Calling .innerHTML() = `<large html structure></>` is much better, but you still need to perform .querySelector() to lookup the newly created HTML and add event listeners to it.

The @event syntax makes it much more clear where the event listener is located compared to the rest of the template. For example…

class MyElement extends LitElement {
  ...
  render() {
    return html`
      <p><button @click="${this._doSomething}">Click Me!</button></p>
    `;
  }
  _doSomething(e) {
    console.log("something");
  }
}

It also makes it much more apparent to me on first glance that event.currentTarget can only be the HTMLElement where you attached the listener and event.target can be the same but also may come from any child of the said HTMLElement. The template also calls .removeEventListener() on its own when the template is removed from the DOM so that’s also one less thing to worry about.

The Status Bar Area

Before I continue explaining the change events that make the status bar work, I would like to highlight one of the drawbacks of the “stateless render”: there isn’t really a neat way to render the initial state of HTML elements. I could add placeholder content for when the input is empty and no selection was made yet, but the render() function only appends the template to the given root. It doesn’t delete siblings within the root so the status bar text would end up being doubled. This could be fixed if I call an initial render somewhere in the custom element, similar to the render calls within the event listeners, but I’ve opted to omit that to keep the example simple.

The input change event is one of the more common change events. It’s straightforward to see that this will be the change event used to calculate and display the updated input length and the number of newlines that the input has.

I thought I would have a much harder time displaying the live status of selected text, but the selectionchange event provides everything I need to calculate the selection status within the textarea. This change event is relatively new too, having only been a part of baseline last September 2024.

Since I’ve already highlighted the two main events driving the status bar, I’ll proceed to the next example.

Stateful Render

My second example is a <pokemon-card> custom-element. The pokemon card component will generate a random Pokémon from a specific pokemon TCG set. The specifications of the web component are as follows:

  • The placeholder will be this generic pokemon card back.
  • A Generate button that adds a new Pokémon card from the TCG set.
  • Left and right arrow buttons for navigation.
  • Text that shows the name and page of the currently displayed Pokémon.

In this example, only two other external libraries were used for the web component that weren’t related to lit and lit-html. I used shuffle from es-toolkit to make sure the array of cards is in a random order each time the component is instantiated. Though the shuffle function itself is likely small enough that you could just write your own implementation in the same file if you want to minimize dependencies.

I also wanted to mention es-toolkit in this article for readers that haven’t heard about it yet. I think it has a lot of useful utility functions so I included it in my example. According to their introduction, “es-toolkit is a modern JavaScript utility library that offers a collection of powerful functions for everyday use.” It’s a modern alternative to lodash, which used to be a staple utility library in every JavaScript project especially during the times before ES6 was released.

There are many ways to implement a random number generator or how to randomly choose an item from a list. I decided to just create a list of all possible choices, shuffle it, then use the pop method so that it’s guaranteed no card will get generated twice. The es-toolkit shuffle type documentation states that it “randomizes the order of elements in an array using the Fisher-Yates algorithm”.

Handling State using Signals

Vanilla JavaScript doesn’t come with a state management solution. While LitElement’s property and state decorators do count as solutions, I want to utilize a solution that I consider should be a part of Vanilla JavaScript just as with lit-html. The state management solution for the component will be JavaScript Signals. Unlike lit-html, signals are already a Stage 1 Proposal so there is a slightly better chance it will become a standard part of the JavaScript specification within the next few years.

As you can see from the Stage 1 Proposal, explaining JavaScript Signals from scratch can be very long that it might as well be its own multi-part article series so I will just give a rundown on how I used it in the <pokemon-card> custom-element. If you’re interested in a quick explanation of what signals are, the creator of SolidJS, which is a popular framework that uses signals, explains their thoughts here.

Signals need an effect implementation to work which is not a part of the proposed signal API, since according to the proposal, it ties into “framework-specific state or strategies which JS does not have access to”. I will be copy and pasting the watcher code in the example despite the comments recommending otherwise. My components are also too basic for any performance related issues to happen anyways. I also used the @lit-labs/signals to keep the component “lit themed” but you can also just use the recommended signal-polyfill directly too.

Signal Syntax

The syntax I used to create a signal state in my custom HTMLElement are as follows:

#visibleIndex = new Signal.State(0)

get visibleIndex() {
  return this.#visibleIndex.get()
}

set visibleIndex(value: number) {
  this.#visibleIndex.set(value)
}

There is a much more concise way to define the above example which involves auto accessors and decorators. Unfortunately, CodePen only supports TypeScript 4.1.3 as of writing, so I’ve opted to just use long-hand syntax in the example. An example of the accessor syntax involving signals is also shown in the signal-polyfill proposal.

Card Component Extras

The Intersection Observer API was used to allow the user to navigate the card component via horizontal scroll bar while also properly updating the state of the current page being displayed.

There is also a keydown event handler present to also let the user navigate between the cards via keyboard presses. Depending on the key being pressed, it calls either the handlePrev() or handleNext() method to perform the navigation.

Finally, while entirely optional, I also added a feature to the component that will preload the next card in JavaScript to improve loading times between generating new cards.

]]>
https://frontendmasters.com/blog/custom-elements-with-lit-html/feed/ 1 8102
Exploring Multi-Brand Systems with Tokens and Composability https://frontendmasters.com/blog/exploring-multi-brand-systems-with-tokens-and-composability/ https://frontendmasters.com/blog/exploring-multi-brand-systems-with-tokens-and-composability/#respond Fri, 19 Dec 2025 16:59:44 +0000 https://frontendmasters.com/blog/?p=8041 Design systems aren’t just about keeping everything looking the same; they’re about making things flexible enough to handle whatever gets thrown at them.

When you’re juggling multiple brands or different use‑cases, a rigid component can feel more limiting than helpful. That’s where tokens, composition, and configuration come in. Tokens let you swap out brand colors and styles without rewriting code, composition gives you building blocks you can remix into new layouts, and configuration keeps all those variations tidy and predictable. Put them together, and suddenly a single component can stretch to fit diverse brands, layouts, and experiences all without breaking from the system.

Let’s look at a typical “Card” component that is designed with all three of these things in mind such that it can support usage across different brands without breaking a sweat.

Setting up the Basic Card Structure

Say our project calls for a simple card. The card has a toggle-able banner that accepts a text string, a thumbnail image, title, description, and a button.

A card showcasing Red Rocks Park and Amphitheatre with a featured location banner, a vibrant image of a concert, title, description, and a button inviting users to view events.

In this article, all examples are written in Vue, but these principles are not Vue specific. They can be used in any design system that builds from components, even native Web Components.

Our component should end up looking something like this:

<template>
  <article class="system-card">
    <div class="system-card-media">
      <span v-if="bannerText" class="system-card-banner">
        {{ bannerText }}
      </span>
      <img :src="imageUrl" :alt="imageAlt" class="system-card-image" />
    </div>

    <div class="system-card-content">
      <h3 class="system-card-title">{{ title }}</h3>
      <p class="system-card-description">{{ description }}</p>
      <button class="system-card-button">{{ buttonText }}</button>
    </div>
  </article>
</template>

<script setup>
defineOptions({ name: "SystemCard" });

defineProps({
  bannerText: { type: String, default: null },
  imageUrl: { type: String, default: "<https://via.placeholder.com/350x150>" },
  imageAlt: { type: String, default: "Card Image" },
  title: { type: String, default: "Card Title" },
  description: {
    type: String,
    default:
      "This is a description of the card content. It provides more details about the card.",
  },
  buttonText: { type: String, default: "Learn More" },
});
</script>

<style scoped>
.system-card {
  background: #fff;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.system-card-media {
  display: flex;
  flex-direction: column;
}

.system-card-banner {
  background-color: #a3533c;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  padding: 4px;
  text-align: center;
  width: 100%;
}

.system-card-content {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ... other card styling, like .system-card-button */
</style>

The Card is then used like this:

<SystemCard
  bannerText="Featured Location"
  imageUrl="image/of/red-rock.jpg"
  imageAlt="Red Rocks Amphitheater during a night concert"
  title="Red Rocks Park and Amphitheater"
  description="There's No Better Place to See The Stars."
  buttonText="View Events"
/>

Supporting Multiple Brands/Themes with Tokens

At the most basic level, we can modify this card to support different brands or themes through the use of tokens or variables. I will be using CSS Custom Properties (variables) for all examples in this article, but tokens are not limited to just CSS, you can learn more about Design Tokens from the Design Tokens Community Group (which just shipped their first stable version!)

All of our existing markup stays the same, but we need to modify the CSS to use variables for configuration. While we’re at it, we should also look at any duplicate or hard-coded values and convert those to variable as well to maintain clean, reusable code.

Our new style declarations should look like this:

<style scoped>
/* These root variables are likely to be set up in a more global stylesheet */
:root {
  --system-card-bg-color: #ffffff;
  --system-card-accent-bg-color: #a3533c;
  --system-card-accent-text-color: #ffffff;

  --system-card-border-radius: 8px;
  --system-card-padding: 16px;
  --system-card-gap: 8px;
}

.system-card {
  background: var(--system-card-bg-color);
  border-radius: var(--system-card-border-radius);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.system-card-media {
  display: flex;
  flex-direction: column;
}

.system-card-banner {
  background-color: var(--system-card-accent-bg-color);
  color: var(--system-card-accent-text-color);
  font-size: 14px;
  font-weight: 600;
  padding: 4px;
  text-align: center;
  width: 100%;
}

.system-card-content {
  padding: var(--system-card-padding);
  display: flex;
  flex-direction: column;
  gap: var(--system-card-gap);
}
...
</style>

Now that our component is using tokens, we can create a custom class and assign those tokens new values. Simply by swapping from statically assigned values to dynamic tokens; CSS Custom Properties in this case, we have enabled our card component to support different visual themes.

<SystemCard
  class="custom-blue-card"
  bannerText="Featured Location"
  imageUrl="image/of/shedd-aquarium.jpg"
  imageAlt="Underwater tunnel at the Shedd Aquarium"
  title="Shedd Aquarium"
  description="Look Nature in the Eye."
  butonText="Plan Your Visit"
/>
.custom-blue-card {
  --system-card-accent-bg-color: #328198;
  --system-card-accent-text-color: #00070B;
}

Alternatively, we could add a new property on the component that assigns a class with new token definitions if we have a known set of themes we want to support:

<SystemCard
  bannerText="Featured Location"
  imageUrl="image/of/shedd-aquarium.jpg"
  imageAlt="Underwater tunnel at the Shedd Aquarium"
  title="Shedd Aquarium"
  description="Look Nature in the Eye."
  butonText="Plan Your Visit"
  theme="light-blue"
/>
/* These would likely be global overrides in a global stylesheet */
.theme--light-blue {
  --system-card-accent-bg-color: #328198;
  --system-card-accent-text-color: #00070B;
}
Card displaying Shedd Aquarium with a featured location banner, an underwater scene, title 'Shedd Aquarium', description 'Look Nature in the Eye.' and a button 'Plan Your Visit'.

Customizing Content with Composable Slots

A description is great, but what if we want to show a list of details instead? In a situation like this we have two options, either maintain unique code for every different variation of the component, or create composable areas within a single component that engineers can write custom code into, in programming these are often referred to as slots.

Taking our code from before:

<template>
  <article class="system-card">
    <div class="system-card-media">
      <span v-if="bannerText" class="system-card-banner">{{ bannerText }}</span>
      <img :src="imageUrl" :alt="imageAlt" class="system-card-image" />
    </div>
    <div class="system-card-content">
      <h3 class="system-card-title">{{ title }}</h3>
      <p class="system-card-description">{{ description }}</p>
      <button class="system-card-button">{{ buttonText }}</button>
    </div>
  </article>
</template>

We can modify our code to replace the .system-card-description element with a slot, we’ll use the name card-details to identify what we expect the contents of this slot to be.

<template>
  <article class="system-card">
    <div class="system-card-media">
      <span v-if="bannerText" class="system-card-banner">{{ bannerText }}</span>
      <img :src="imageUrl" :alt="imageAlt" class="system-card-image" />
    </div>
    <div class="system-card-content">
      <h3 class="system-card-title">{{ title }}</h3>
      <slot name="card-details" />
      <button class="system-card-button">{{ buttonText }}</button>
    </div>
  </article>
</template>

For the existing cards with a tagline we can simply place the .system-card-description element within the slot to achieve the same result.

<SystemCard
  bannerText="Featured Location"
  imageUrl="image/of/shedd-aquarium.jpg"
  imageAlt="Underwater tunnel at the Shedd Aquarium"
  title="Shedd Aquarium  butonText="Plan Your Visit"
  theme="light-blue"
>
<!-- This is our slot -->
<p class="system-card-description">Look Nature in the Eye.</p>
<!-- This is our slot -->
</SystemCard>
A card component showcasing the Shedd Aquarium with a featured location banner, an underwater scene, and a call-to-action button.

By creating a slot however, we’ve now opened up the possibility for custom content within in. The team can now create a snippet of code to display the list of highlights producing a unique variation on the existing component without completely breaking away from the system.

Component usage could then look like this:

<SystemCard
  bannerText="Featured Location"
  imageUrl="image/of/shedd-aquarium.jpg"
  imageAlt="Underwater tunnel at the Shedd Aquarium"
  title="Shedd Aquarium  butonText="Plan Your Visit"
  theme="light-blue"
>
  <!-- This is our slot -->
  <ul class="highlight-list">
    <li class="highlight-item">
      <p class="highlight-text">Touch Experiences</p>
    </li>
    <li class="highlight-item">
      <p class="highlight-text">Animal Encounters</p>
    </li>
    <li class="highlight-item">
      <p class="highlight-text">Stingray Feedings</p>
    </li>
  </ul>
  <!-- This is our slot -->
</SystemCard>

Producing a result like this:

A card design showcasing the Shedd Aquarium with a featured location banner, an underwater scene, and a list of available experiences including 'Touch Experiences', 'Animal Encounters', and 'Stringray Feedings'. Below, there is a button labeled 'Plan Your Visit'.

A Note on Keeping Things Organized

The concept of completely free and open slots is likely terrifying to a designer or engineer who is focused on maintaining clean and organized code since now we’re allowing people to add whatever custom work they want to an area.

To avoid this we can provide “ready-made” child components that we would prefer teams use in these areas, whether through a predetermined list of parts that we know consumers will want, or by paying attention to and adopting repeated usage patterns.

In our examples so far we know we want to support system-card-description and a new system-card-list as options for this space; we can create those as smaller components or “partials.” Engineering and design teams can then use those formally adopted and verified options when they fit their needs, and they maintain all of the benefits of using system components over needing to create custom solutions.

Using these partials might look like this:

<SystemCard
  bannerText="Featured Location"
  imageUrl="image/of/shedd-aquarium.jpg"
  imageAlt="Underwater tunnel at the Shedd Aquarium"
  title="Shedd Aquarium  butonText="Plan Your Visit"
  theme="light-blue"
>
  <!-- This is our slot -->
  <SystemCardDescription text="Look Nature in the Eye.">
  <!-- This is our slot -->
</SystemCard>

or:

<SystemCard
  bannerText="Featured Location"
  imageUrl="image/of/shedd-aquarium.jpg"
  imageAlt="Underwater tunnel at the Shedd Aquarium"
  title="Shedd Aquarium  butonText="Plan Your Visit"
  theme="light-blue"
>
  <!-- This is our slot -->
  <SystemCardList items="[array of items]">
  <!-- This is our slot -->
</SystemCard>

Extending Configuration and Composability for Further Customization

Using slots and partials for configuration and composition is not limited to single areas within a component either, once you start thinking in this model you can create incredibly flexible components that can support a vast array of different styles and layouts.

Examining our card component through the lenses of composition and configuration we can create a layout like this.

An aerial view of the Adler Planetarium showcasing its unique dome structure and surrounding landscape, featuring a prominent banner reading 'Escape to the Stars'. Below, there is a section displaying the title 'Adler Planetarium' and a list of upcoming events.

The first thing we need to do is identify what pieces are configurable and what pieces are composable. Configurable elements are typically controlled through attributes, where composable sections are typically slots.

In our case the location of the .card-media__banner can be configurable to either the top or bottom of the image.

As for composition, we’ve taken the entire system-card-content area and turned it into a slot, allowing users of the component to build out whatever layout meets their needs. In this case we’re putting the button first, followed by the title, a list of details, and social links..

The component code now looks like this:

<template>
  <article class="system-card">
    <CardMedia
      :bannerText="bannerText"
      :bannerLocation="bannerLocation"
      :imageUrl="imageUrl"
      :imageAlt="imageAlt"
    />
    <div class="system-card-content">
      <slot name="card-content" />
    </div>
  </article>
</template>

<script setup>
import CardMedia from "./partials/CardMedia.vue";

defineOptions({ name: "SystemCard" });

defineProps({
  bannerText: { type: String, default: null },
  bannerLocation: { type: String, default: "top" },
  imageUrl: { type: String, default: "<https://via.placeholder.com/350x150>" },
  imageAlt: { type: String, default: "Card Image" },
  title: { type: String, default: "Card Title" },
  description: {
    type: String,
    default:
      "This is a description of the card content. It provides more details about the card.",
  },
  buttonText: { type: String, default: "Learn More" },
});
</script>

and in use:

<SystemCard
  bannerText="Escape to the Stars"
  bannerLocation="bottom"
  imageUrl="image/of/adler-planetarium.jpg"
  imageAlt="Aerial view or Adler Planetarium"
>
  <SystemButton type="filled" text="Join Us" iconEnd="arrow-right" />
  <SystemCardTitle text="Adler Planetarium" />
  <SystemCardList
    title="Upcoming Events and Shows"
    items="[array of items]">
    <SystemCardSocials
      facebook="link.to.social"
      twitter="link.to.social"
      youtube="link.to.social"
      instagram="link.to.social"
    />
  </SystemCardList>
</SystemCard>

We’ve moved the banner and image to a new partial we’re importing called CardMedia that partial is then passed the prop bannerLocation and determines whether the banner should appear on the top of bottom of the image. Now, because our entire content area is a slot we’ve added multiple elements as children of the SystemCard; we’ve got a SystemButton, SystemCardTitle, SystemCardSocial, and our SystemCardList from before, in a new layout that we have defined ourselves all while continuing to use the design system without breaking.

Our final result is a highly adaptable card that supports all of our examples through the use of configuration through props and configuration through custom slots.

Three card components showcasing different featured locations with images, titles, descriptions, and action buttons.

Turning Principles into Practice

Supporting multiple brands and use‑cases doesn’t have to mean duplicating components or maintaining endless forks of code. By grounding your system in tokens, composition, and configuration, you can keep one core component flexible enough to handle divergent needs.

  • Tokens: Centralize design decisions like color, spacing, and typography so brand shifts are a matter of swapping variables or themes, not rewriting CSS.
  • Composition (slots/partials): Create structured areas where teams can plug in approved variations, reducing the need for custom one‑offs while still allowing an escape hatch when needed.
  • Configuration (props/attributes): Expose common and repeated options for styles, layouts, and behavior so components adapt without breaking consistency.

The payoff is huge: fewer bespoke components to maintain, faster delivery across brands, and a system that scales without losing cohesion. Instead of fighting divergence, you’re designing for it, and that’s how systems stay resilient in the real world.

]]>
https://frontendmasters.com/blog/exploring-multi-brand-systems-with-tokens-and-composability/feed/ 0 8041
The Joy of Mixing Custom Elements, Web Components, and Markdown https://frontendmasters.com/blog/the-joy-of-mixing-custom-elements-web-components-and-markdown/ https://frontendmasters.com/blog/the-joy-of-mixing-custom-elements-web-components-and-markdown/#respond Thu, 11 Sep 2025 15:17:35 +0000 https://frontendmasters.com/blog/?p=7137 One of the nice things about Markdown is that you can just… put HTML in there too. There is no Markdown shortcut for a <div>, but you can just use a <div>. That means you can use use <my-custom-element> as well, bringing the world of Web Components into your writing and creating of content.

Deane Barker writes:

What if you want a Markdown-friendly way to represent a feedback form, or a tabbed UI, or a mortgage calculator, or something else way beyond the scope of text formatting.

For these situations, what you really want is to put a token or a placeholder in your Markdown, and have something else expand it later on into a larger, more complicated HTML construct.

What you sort of need is… Markdown for More Complicated HTML™.

Enter Custom Elements.

If you want to put React components into Markdown, you’ve got MDX, but MDX can get Very Complicated™. If you just want to use Web Components in Markdown, well, Dave really said it best.

]]>
https://frontendmasters.com/blog/the-joy-of-mixing-custom-elements-web-components-and-markdown/feed/ 0 7137
A Color Input That Also Shows the Value https://frontendmasters.com/blog/a-color-input-that-also-shows-the-value/ https://frontendmasters.com/blog/a-color-input-that-also-shows-the-value/#comments Tue, 18 Feb 2025 15:01:47 +0000 https://frontendmasters.com/blog/?p=5212 It’s awfully nice that HTML provides a native color picker. Activating the input opens up a color picker (either the OS-provided one or something the browser provides), allows you to pick a color, and changes the input’s value to that color1.

Here’s what that’s like in macOS Chrome:

The UI varies, but in all cases it doesn’t actually show you the color value you’ve picked when the picker is closed. I think that’s… weird? What if the input is part of a form in which you actually have a valid color you want to put in yourself? Or copy the value out of?

I thought of this while I was looking at Adam Argyle’s color-mix() tool. It’s a great tool, but it made me wish I could just type or paste in a color rather than having to use the picker.

I figured I’d toss together a Web Component that would actually display the color. We could call it an HTML web component as it starts with perfectly valid HTML (which you can customize as needed) then you wrap it in a custom element to extend the functionality and/or UI. In this the thing that displays the color is an <input type="text">, because that works both to show it, and can accept a value that can propagate back to the color input.

That basically does what I was picturing. This keeps it all Light DOM so it would be quite easy to style and customize. Since could be used inside a <form>, you might need to fiddle with ElementInternals so that the input can participate in the form as expected. Since there are now two inputs that essentially have the same value, it’s likely you’ll only want one to submit as form data.

But my example there, like native color inputs themselves, deals exclusively in HEX colors. I was hoping that the text input could deal in any sort of valid color format.

Erick Merchant had a clever idea where the color from the text input is coerced into a HEX for the benefit of the color input, but otherwise accepts and valid color value. Try putting oklch(64.26% 0.3059 332) into this:

Pretty clever if you ask me! It won’t handle transparency, so that’s something to consider for sure, but otherwise seems to do a pretty good job. I’d be tempted to take the color inputs value in a form generally, as it has automatic validation to ensure it’s a valid color. But in the case of this second demo, I’d be tempted to take the text input value instead since it honors the original intention of the color, albeit very hard to validate.

  1. It would be extremely cool if OS color pickers supported formats other than HEX as well as P3-and-beyond color spaces, but that’s a topic for another day. ↩︎

]]>
https://frontendmasters.com/blog/a-color-input-that-also-shows-the-value/feed/ 3 5212
Bluesky Comments on any Post https://frontendmasters.com/blog/bluesky-comments-on-any-post/ https://frontendmasters.com/blog/bluesky-comments-on-any-post/#respond Mon, 13 Jan 2025 22:34:52 +0000 https://frontendmasters.com/blog/?p=4962 ) that loads up all the replies to any particular post like a comment thread. Imagine there is a post for […]]]> Bluesky is enjoying a boon in popularity. The API access right now is nicely open, allowing people to create some interesting stuff around it. I like this idea from Matt Kane: a Web Component (<bluesky-comments>) that loads up all the replies to any particular post like a comment thread. Imagine there is a post for every blog post you publish (like we do), instead of writing your own comment system you could just say “leave a reply on Bluesky” and that could be the whole commenting system. I hope get gets proper filtering.

]]>
https://frontendmasters.com/blog/bluesky-comments-on-any-post/feed/ 0 4962
React 19 and Web Component Examples https://frontendmasters.com/blog/react-19-and-web-component-examples/ https://frontendmasters.com/blog/react-19-and-web-component-examples/#respond Mon, 16 Dec 2024 16:37:38 +0000 https://frontendmasters.com/blog/?p=4800 There is lots of news of React 19 and going stable and now supporting Web Components. Or… “custom elements” I should say, as that refers to the HTML expression of them as <dashed-elements>, which is where the trouble laid. Apparently it was hard for React to know, in JSX, if props should be treated as a property or an attribute. So they’ve just decided this is how it will work:

  • Server Side Rendering: props passed to a custom element will render as attributes if their type is a primitive value like stringnumber, or the value is true. Props with non-primitive types like objectsymbolfunction, or value false will be omitted.
  • Client Side Rendering: props that match a property on the Custom Element instance will be assigned as properties, otherwise they will be assigned as attributes.

That’s enough to pass all the tests at Custom Elements Everywhere, which tracks such things. (And apparently every single framework is now 100% fine. Cool.)

This got me thinking about what this actually means and how I might make use of it. I use both React and Web Components sometimes, but only rarely do I combine them, and the last time I did I had more issues with the Shadow DOM than I did with React doing anything funky.

So here I’ve made a LitElement and rendered it within a React component:

What I was thinking there was… what if I make a <designsystem-button> and need a click handler on it? Turns out that’s not really a problem. You can just slap a React onClick right on it and it’s fine.

<MyCard>
  <p>blah blah</p>
  <!-- this is fine -->
  <designsystem-button onClick={() => {}}></designsystem-button>
</MyCard>

That wasn’t a problem anyway, apparently.

What is a problem is if I want to send in a function from React-land for the Web Component to call. You’d think we could send the function in how LitElement generally wants you to do that like:

<!-- nope -->
<designsystem-button .mySpecialEvent=${specialEvent}>

But nope, JSX really doesn’t like that “dot syntax” and won’t compile.

So you gotta send it in more like this:

<designsystem-button onSpecialEvent={() => mySpecialEvent()}

Then in order to “call” that event, you “dispatch” and event named properly. Like:

this.dispatchEvent(new CustomEvent("SpecialEvent", { bubbles: true }));

Here’s that with a “raw” Web Component:

I took that idea from Jared White’s article Oh Happy Day! React Finally Speaks Web Components. Where he’s got some other examples. Another is passing in a “complex object” which is one of those things that would have been impossible in React 18 and under apparently, and now we can do:

]]>
https://frontendmasters.com/blog/react-19-and-web-component-examples/feed/ 0 4800
player.style https://frontendmasters.com/blog/player-style/ https://frontendmasters.com/blog/player-style/#respond Thu, 14 Nov 2024 14:46:56 +0000 https://frontendmasters.com/blog/?p=4422 These custom video (and audio) players are very nice. I like how the accommodate narrow/vertical video players and work with any video provider (even YouTube).

We might be shooting for the stars, but we’re also building on top of the magical media chrome ecosystem of player components, which does a few things for us:

  1. It uses web components which are compatible across web dev frameworks. Yes, even React (which can be grumpy about web components).
  2. It allows us to separate the player UI from the player streaming technology, so skins can be used across “players” rather than being player-specific.

]]>
https://frontendmasters.com/blog/player-style/feed/ 0 4422
9 / 16 https://frontendmasters.com/blog/9-16/ https://frontendmasters.com/blog/9-16/#comments Thu, 24 Oct 2024 14:01:29 +0000 https://frontendmasters.com/blog/?p=4259 The other day I needed to quickly see pixel dimensions that were exactly in a 9 / 16 aspect ratio. Like: 180 / 320. That’s perfectly in that ratio. You might be able to think of that one in your head, but how about 351 / 624? That’s harder to think of. And I wanted to visualize it somehow, so I ended up making a web component (<aspect-ratio-machine>) that would display a resizable box in that exact aspect ratio (thanks to CSS’ aspect-ratio) then also display the pixel dimensions as well.

That’s a light-DOM-only web component that just “enhances” otherwise normal HTML. Perhaps it’s not bursting with usefulness, but it was useful to me.

The reason it was useful, by the way, is that I was playing around with YouTube Shorts, and the thing I was using to upload was very strict that the video was exactly 9 / 16 ratio. I wanted to record somewhat arbitrary sections of my screen, then crop and arrange and stuff later into a size that accommodated 9 / 16, and I had to type in the pixel dimensions manually in the particular software I was using to get there.

I also needed integers. A failing of my first example above is that the resizer was happy to do sub-pixel values, which were not useful to me as the software I was using to make a video canvas size did not accept them.

I also realized that actually seeing a box in the 9 / 16 size wasn’t particularly useful. I know what a rectangle looks like. So instead a built a more simple solution with a range slider that updated numbers on the screen.

Niche problem; niche solution. But hey I like building little tools for myself so I thought I’d share. “Home Cooked Apps” as it were.

]]>
https://frontendmasters.com/blog/9-16/feed/ 1 4259
Small Example of Declarative Shadow DOM https://frontendmasters.com/blog/small-example-of-declarative-shadow-dom/ https://frontendmasters.com/blog/small-example-of-declarative-shadow-dom/#comments Tue, 15 Oct 2024 14:22:01 +0000 https://frontendmasters.com/blog/?p=4186 I thought the Bytes newsletter #326 did a good job of showing the different between a “normal” web component and a “declarative shadow DOM” web component. (Quick ports of the former and latter). Declarative shadow DOM is the web components story for “server-side rendering” (SSR). All the visual stuff needed to render the component is in the HTML, then JavaScript loads later and wires it all up. Literally the same as hydration. I’ve heard “where is the Next.js of web components?” and I tend to think declarative shadow DOM is one of the major primitives required to get there.

]]>
https://frontendmasters.com/blog/small-example-of-declarative-shadow-dom/feed/ 1 4186
Web Components are Bad, Good, and OK https://frontendmasters.com/blog/web-components-are-bad-good-and-ok/ https://frontendmasters.com/blog/web-components-are-bad-good-and-ok/#respond Thu, 03 Oct 2024 14:18:24 +0000 https://frontendmasters.com/blog/?p=4111
  • Papa bear’s porridge is hot. Web Components Are Not the Future: “Web Components possibly pose the biggest risk to the future of the web that I can see.” — Ryan Carniato
  • Mama bear’s porridge is cool. Web Components Are Not the Future — They’re the Present: “If you’re convinced that your way is the best and only way, it’s natural to feel disenchanted when a decision is made that you don’t fully agree with.” — Cory LaViska
  • Baby bear’s porridge is jusssst right. Web components are okay: “I find these debates a bit tiresome. I think the fundamental issue, as I’ve previously said, is that people are talking past each other because they’re building different things with different constraints.” — Nolan Lawson
  • There is a meta conversation of “Web Components are very definitely going to be around 10 years from now while any JavaScript framework today will not” is worth considering, but pits the two directly against each other when we really don’t need to.

    ]]>
    https://frontendmasters.com/blog/web-components-are-bad-good-and-ok/feed/ 0 4111