Lots to shout about in Quiet UI

  From the homepage of Quiet UI, a mouse mascot in a hoodie using a laptop with the text: A UI library for the Web focusing on accessibility, longevity, performance, and simplicity

As President of Web Components, it’s my duty to publicly comment on every Web Component library and framework that exists. Today I’m taking a look at Quiet UI, a new source-available web component library soft-launched by Cory LaViska, the creator of Shoelace WebAwesome. You might be asking “another UI library? But why?” and that’s a good question, I’ll let Cory tell you in his own words

I wanted to play with bleeding edge features that weren’t available in all browsers yet… I wanted to take everything I learned from developing components over the years and challenge some ideas, try new things, and bake in opinions that I’ve traditionally veered away from. It felt liberating.

“Play” as a foundation is compelling to me. A lot of writing and ancient programming advice says “Write the thing, then throw it away and write it again” and while that sounds like an incredible waste of time, your second draft understands the problem set better than the first and you can make smarter/different decisions. And as Cory points out, the last half-decade has been a heyday for Web APIs and browser interop, which means your components can be more robust with less code. Whether you use web components or not, it’s a good time to re-evaluate your component system and do some quality-of-life upgrades.

Peeking at what’s inside the box of what Quiet UI has to offer, I’ve found some interesting concepts beyond the industry standard set of components. Let’s take a look…

Theming system

A design token theming system is pretty standard fare for a component system. Out of the box you get a generous set of harmonious static color primitives all based on color-mix() to generate a consistent palette.

A 10-step color pallette with four tiers cof colors: primary, neutral, constructive, destructive

With the static primitives, you get a set of “Adaptive Colors” for text, fill, and stroke colors. Rather than a numeric ramp, this ramp is a 5-stop vibrancy/loudness scale and each color ramp adapts to light and dark modes.

The same four-tier color pallet but only 5-steps of colors

It’s tempting to have an 11-step color ramp and then think your adaptive color ramp needs to also be 11-steps, but based on personal experience that leads to more contrast problems than it’s worth, so limiting the adaptive light/dark colors to 5-steps and the border and text ramps to 3-steps is a good idea. I applaud the restraint that went into that decision.

It’s a minor thing aspect, but naming the color collections “primary”, “neutral”, “destructive”, and “constructive” are nice, semantic –yet generic– buckets for values. It wouldn’t be too difficult to add one or two more collections for extra spice.

Restyle native elements

Quiet’s “Restyle” stylesheet has a lot of appeal to me. It’s a cross between a CSS Reset and a default stylesheet to theme native elements to look like your design system components.

An HTML form with different types of inputs all custom styled

These are all plain ol' HTML

I could see this as a nice offering so consumers of your design system can use regular ol’ HTML alongside the first-party components and it’ll all maintain the same look and feel because they’re using the same underlying token architecture.

Adding to the base theme and restyle, you also get some global CSS utilities to glue everything together.

Useful utilities

The CSS utilities are nice but Quiet UI goes a bit further and offers a handful of helpful JavaScript utilities in the form of web component wrappers.

Now, I’m the kind of idiot who wants to learn how to handwrite observers, then not use them for awhile and forget how they work, then have to re-learn observer patterns from scratch every two years… but I could see how others would not want to do that.

Abstracting away some of the more painful learning curves through a thin, declarative web component wrapper API seems like a smart decision.

Components and gimmicks galore!

Inside Quiet UI is an impressive number of components for a side project. There’s all the standard UI components like Accordion, Breadcrumbs, Cards, Dialog, etc. The documentation breaks off Form controls into its own little section, which makes sense because form-associated custom elements are a bit unique in webcomponents-land.

But what I want to call special attention to is what I will lovingly refer to as “Gimmick Components”. A gimmick sounds bad, like a cheap trick, but I mean it in the “Aww, that’s cool, they didn’t have to do that, but that’s cool” sort of way. Quiet UI bundles tons of little non-everyday, nice-to-have components into the kit. As you start digging through the LEGO bin, the mental image of what you could build starts growing…

  • Browser Frame - A one-off to frame screenshots or make a section feel more web-like.
  • Comparison - Don’t always need a responsive image compare tool, but I know I don’t want to build my own.
  • Expander - Truncation happens… and it’s nice to have a good option right out of the box.
  • Flip Card - Few know how to master this CSS-trickery.
  • Joystick - A design system with a joystick? Novel.
  • QR Code - It’s Friday and the marketing team needs a website by Monday.
  • Slide Activator - Slide to activate! In a website!
  • Sparkline - A matter of time before someone asks for a baby chart.
  • Random Content - Spicy Lorem Ipsum!
  • Timed Content - The holiday campaign goes live when you’re asleep and must end when you’re unwrapping presents with your family. And there’s a code freeze.
  • Zoomable Frame - Browse infinite canvases with ease.

And there’s a whole collection of smaller components dedicated to text formatting.

  • Bytes - This isn’t hard to do, but I’d rather have a component and call it done.
  • Countdown - 8 out of 10 cats recommend this plugin. Check it out before the timer reaches zero.
  • Fit Text - Feel like I’ve heard about this somewhere…
  • Number - I had an Intl.NumberFormat issue the other day, would have been nice not to have that issue.
  • Number Ticker - Bosses love number go up!
  • Relative Time - Again, Intl.RelativeTimeFormat… not the funnest one to get sucked into.
  • Text Mask - An old effect, but a goodie.
  • Typewriter - Taka-taka-taka-tak. Look like your favorite generative AI chat bots.

Like I said, there’s a lot of gimmicks inside of here. A lot of these are already available (or could be) as “Standalone” components, but bundled in with a consistent styling API makes it all that much nicer.

I browse a lot of design systems everyday and they’re all the same boring collection of 20-30 components with different levels of Bootstrap / Material / Tailwind / ShadCN flavoring mixed in. These types of random, not-solely utilitarian components elevate Quiet UI above the pack.

The gimmicks create an atmosphere of “Play” that’s part of Quiet UI’s foundation. It culminates into a feeling of “fun” instead of business, business, business. It ticks a box in my brain that if my UI needs some pizzazz or something a little unconventional like a countdown timer or a number ticker on a random Tuesday in November… Quiet UI might be a good base to build on. I’m seriously considering making this the default component set for all side projects going forward.

Quiet UI? Oh, that’s the fun one.

daverupert.com

16 Oct 2025 at 06:08

The killer feature of Web Components

 A left right flow chart starting at Add JSDoc pointing to CEM analyze then branching off into 8 different directions: API documentation, Storybook, Language Server, Linter, React Wrappers, More Wrappers, Jest Mocks, JSX Types, Figma Code Connect, and MCP Server

One unsung feature in the web components space that I don’t think gets enough attention is the Custom Elements Manifest initiative. I think it’s the killer feature of web components.

Known as “CEM” to its friends, a CEM is a community standard JSON format that surfaces information about your component APIs. The analyzer scans your class-based component to build up a “manifest” of all the methods, events, slots, parts, tag name, and CSS variables you want to expose. It works on a single component or an entire system’s worth of components. If you want to surface more details to consumers (like accepted attributes or CSS custom properties), you can provide more context to the analyzer through JSDoc comments and/or TypeScript types – which is good code hygiene and a favor for your future self anyhow. Here’s an example from the playground:

/**
 * @attr {boolean} disabled - disables the element
 * @attribute {string} foo - description for foo
 *
 * @csspart bar - Styles the color of bar
 *
 * @slot - This is a default/unnamed slot
 * @slot container - You can put some elements here
 *
 * @cssprop --text-color - Controls the color of foo
 * @cssproperty [--background-color=red] - Controls the color of bar
 *
 * @prop {boolean} prop1 - some description
 * @property {number} prop2 - some description
 *
 * @fires custom-event - some description for custom-event
 * @fires {Event} typed-event - some description for typed-event
 * @event {CustomEvent} typed-custom-event - some description for typed-custom-event
 *
 * @summary This is MyElement
 *
 * @tag my-element
 * @tagname my-element
 */
class MyElement extends HTMLElement {}

The JSDoc notation is forgiving (it supports both @cssprop and @cssproperty) and with the ability to document your ::part() and <slot> APIs, it’s more descriptive than what you’d get with a basic TypeScript interface. Eagle-eyed observers will notice there’s a distinction made between an @attribute and an @property, that’s because those are different concepts in HTML, ergo different in Custom Elements. Attributes (strings, numbers, booleans) tend to reflect, properties don’t.

After that thin layer of documentation, it’s a two-liner to generate the manifest:

npm i -D @custom-elements-manifest/analyzer
cem analyze

This will generate a file called custom-elements.json in your package directory.

View Sample Output

{
    "schemaVersion": "1.0.0",
    "readme": "",
    "modules": [
        {
            "kind": "javascript-module",
            "path": "src/my-element.js",
            "declarations": [
                {
                    "kind": "class",
                    "description": "",
                    "name": "MyElement",
                    "cssProperties": [
                        {
                            "description": "Controls the color of foo",
                            "name": "--text-color"
                        },
                        {
                            "description": "Controls the color of bar",
                            "name": "--background-color",
                            "default": "red"
                        }
                    ],
                    "cssParts": [
                        {
                            "description": "Styles the color of bar",
                            "name": "bar"
                        }
                    ],
                    "slots": [
                        {
                            "description": "This is a default/unnamed slot",
                            "name": ""
                        },
                        {
                            "description": "You can put some elements here",
                            "name": "container"
                        }
                    ],
                    "members": [
                        {
                            "kind": "field",
                            "name": "disabled"
                        },
                        {
                            "kind": "method",
                            "name": "fire"
                        },
                        {
                            "type": {
                                "text": "boolean"
                            },
                            "description": "some description",
                            "name": "prop1",
                            "kind": "field"
                        },
                        {
                            "type": {
                                "text": "number"
                            },
                            "description": "some description",
                            "name": "prop2",
                            "kind": "field"
                        }
                    ],
                    "events": [
                        {
                            "name": "disabled-changed",
                            "type": {
                                "text": "Event"
                            }
                        },
                        {
                            "description": "some description for custom-event",
                            "name": "custom-event"
                        },
                        {
                            "type": {
                                "text": "Event"
                            },
                            "description": "some description for typed-event",
                            "name": "typed-event"
                        },
                        {
                            "type": {
                                "text": "CustomEvent"
                            },
                            "description": "some description for typed-custom-event",
                            "name": "typed-custom-event"
                        }
                    ],
                    "attributes": [
                        {
                            "name": "disabled",
                            "type": {
                                "text": "boolean"
                            },
                            "description": "disables the element"
                        },
                        {
                            "type": {
                                "text": "string"
                            },
                            "description": "description for foo",
                            "name": "foo"
                        }
                    ],
                    "superclass": {
                        "name": "HTMLElement"
                    },
                    "tagName": "my-element",
                    "customElement": true,
                    "summary": "This is MyElement"
                }
            ],
            "exports": [
                {
                    "kind": "custom-element-definition",
                    "name": "my-element",
                    "declaration": {
                        "name": "MyElement",
                        "module": "src/my-element.js"
                    }
                }
            ]
        }
    ]
}

API extraction through TypeScript or JSDoc isn’t a novel concept, but what I find novel is the community tooling built around it. With a Custom Element Manifest, community plugins can use that information to generate files, populate dropdowns, add red squiggles, provide autocomplete, and automate a lot of the mundane meta-system DX work that comes with supporting a component library:

  • API Documentation - Your CEM can power your readme.md and component-level documentation.
  • Storybook - Using the Storybook plugin you can use your CEM to automate the generation of your Storybook stories.
  • Language Servers - It can be frustrating to not have your editor recognize the HTML you invented. CEM-powered language servers solve that issue. A few options here.
  • Linter - Want to lint HTML before shipping? CEM can power this.
  • React Wrappers - React 19 supports web components, but if you’re trying to use web components in a React <= 18 project, you’ll need little wrapper shims. Creating these by hand isn’t fun and we can hand this work off to the CEM.
  • Other Framework Wrappers - Shims for SolidJS, Svelte, and Vue.js.
  • JSX Types - JSX isn’t happy unless you add new interfaces to the JSX namespace. CEM can generate this for you.
  • Jest Mocks - If you’re still using Jest in the year of our lord 2025, I feel bad for you but there are plenty of people in this situation. Jest hates Custom Elements because Custom Elements are real DOM and not VDOM. No plugin to link to (sorry!) but I have seen teams using CEM to generate Jest mocks to smooth over the process when integrating with legacy testing solutions.
  • Figma Code Connect - If you want to connect your web components to Figma, you can automate that as well.
  • MCP Server - If you want to give your AI Agents insights into the components available in the current project, you can install an MCP Server that parses your CEM. (Couple options here)

Burton Smith who runs WC Toolkit has been helping us roll out some of our CEM work and we’re starting to turn some of these capabilities on. One pain point I’m hoping to solve is too much boilerplate. We have a lot of files in our individual component packages to power assorted tasks and integrations and I can see a world where we generate nearly all our readmes, storybooks, and even some low-level test coverage from the CEM at build-time or run-time.

From a single file we get the following outcomes…

  • Spend less time/energy maintaining and schlepping boilerplate code
  • Improve baseline test coverage and make it more predictable
  • Make new components easier to create, ideally speeding up development
  • Reduce cognitive overhead when jumping into the project
  • Provide a forcing function for more/better documentation
  • Potentially improve the design-developer bridge through Figma Code Connect and MCPs

Again, I want to applaud the web components community here. There’s no VC-funded corporate overlord roadmap driving the Custom Elements Manifest initiative, just fellow enthusiasts. From a community perspective, that’s a positive signal for me. Adding it to your project is low-effort, high-impact type work and I probably only covered about a quarter of what a CEM can do for you, which goes to show a community agreeing on a standardized way to describe components is a powerful tool.

daverupert.com

13 Oct 2025 at 16:30



Refresh complete

ReloadX
Home
(76) All feeds

Last 24 hours
Download OPML
Annie
*
Articles – Dan Q
*
Baty.net posts
bgfay
*
Bix Dot Blog
*
Brandon's Journal
Chris McLeod's blog
Colin Devroe
*
Colin Walker – Daily Feed
Content on Kwon.nyc
Crazy Stupid Tech
*
daverupert.com
*
Human Stuff from Lisa Olivera
*
jabel
James Van Dyne
*
Jim Nielsen's Blog
Jo's Blog
Kev Quirk
*
Manton Reece
*
Manu's Feed
*
Notes – Dan Q
*
On my Om
*
QC RSS
rebeccatoh.co
*
Rhoneisms
*
Robert Birming
*
Scripting News for email
Simon Collison | Articles & Stream
strandlines
*
The Torment Nexus
*
thejaymo

About Reader


Reader is a public/private RSS & Atom feed reader.


The page is publicly available but all admin and post actions are gated behind login checks. Anyone is welcome to come and have a look at what feeds are listed — the posts visible will be everything within the last week and be unaffected by my read/unread status.


Reader currently updates every six hours.


Close

Search




x
Colin Walker Colin Walker colin@colinwalker.blog