Using Components
Quiet includes a collection of Web Components, or custom HTML elements. They have attributes, properties, events, and methods, all of which are described in the documentation for each component.
To use the components, install them using the instructions found on the installation page.
Once installed, simply add the components you want to your HTML.
<quiet-button> I'm a button </quiet-button>
You can obtain references using document.querySelector()
and
document.getElementById()
just like a native HTML element.
const button = document.querySelector('quiet-button');
Some HTML elements, such as <img>
, are
void elements, meaning you can omit their closing tag. Custom elements cannot be void elements, so make sure you
always close their tags!
Awaiting registration Jump to heading
Since custom elements are indifferent to frameworks, there's no initialization phase to ensure components
have been registered before using them. Thus, before accessing properties or methods on custom elements, you
need to ensure they're defined first. You can use
customElements.whenDefined()
for this.
await customElements.whenDefined('quiet-button'); // <quiet-button> is defined and ready to use!
However, this can be cumbersome when you have a lot of components on the page. For convenience, Quiet
provides an allDefined()
function that waits for all Quiet components that are currently on the
page to finish registering.
import { allDefined } from 'https://cdn.jsdelivr.net/npm/@quietui/quiet-browser@1.0.0/dist/quiet.js'; await allDefined(); // All `<quiet-*>` elements defined and are ready to use!
import { allDefined } from '@quietui/quiet'; await allDefined(); // All `<quiet-*>` elements defined and are ready to use!
You can use this function in the beginning of your app to wait for components to load up front, or you can use it on demand whenever you need to access a component's properties or methods with JavaScript.
Advanced usage
You can provide options to change how this function works, allowing you to listen for other custom elements and/or custom elements that may not be on the page when the function is called. All available options are shown below.
await allDefined({ // Wait for all `<quiet-*>` elements on the page match: tagName => tagName.startsWith('quiet-'), // Also wait for `<foo-element>` and `<bar-element>` additionalElements: ['foo-element', 'bar-element'], // Look in the current document (can also be a shadow root) root: document });
This function will throw if a registration fails for any reason, so you'll probably want to wrap it in a
try...catch
block!
Attributes & properties Jump to heading
Most components have properties that can be set with attributes. For example, buttons have a
variant
attribute that let's you change the type of button that gets rendered.
<quiet-button variant="primary">Primary</quiet-button> <quiet-button variant="destructive">Destructive</quiet-button>
Some properties are Boolean, meaning they only accept true or false values. Boolean properties are false
when the attribute is absent and true when the attribute is present. For example, you can disable a button
by adding the disabled
attribute. To enable the button later on, remove the
disabled
attribute.
<quiet-button disabled> Disabled </quiet-button>
Some attributes will reflect, or be added/removed automatically, when setting the property via JavaScript. You can determine if an attribute reflects by looking at the properties table in the component's documentation.
The updateComplete
property
Jump to heading
Components batch DOM updates for performance, so you might run into scenarios where you update a property
but don't see the changes in the DOM right away. In this case, you can await the
updateComplete
property, which is available on every component.
Here's an example where we update a progress bar's value and inspect the corresponding attribute. Note how
the attribute isn't updated until awaiting updateComplete
.
<quiet-progress value="0"></quiet-progress> <script> const progress = document.querySelector('quiet-progress'); progress.value = 100; // outputs "0" console.log(progress.getAttribute('value')); await progress.updateComplete; // outputs "100" console.log(progress.getAttribute('value')); </script>
If you're updating multiple elements, you can safely use
requestAnimationFrame()
instead of awaiting every individual component.
Slots Jump to heading
Many components accept content through slots. Slots are a platform feature that work very similar to the slots you may have used in Vue. A custom element can have any number of slots.
The default slot is almost any content inside the component. In this example, we're slotting a text node into the button to serve as its label, but it could be an HTML element as well.
<quiet-button> Like </quiet-button>
However, some components also have named slots. To insert content into a named slot, add the
slot
attribute to any top-level child element of the component. The position of
the slotted content doesn't matter, as long as it's not nested within another element. The component will
automatically render the slotted content in the correct location.
<quiet-button> <quiet-icon slot="start" name="thumb-up"></quiet-icon> Like </quiet-button> <quiet-button> Like <quiet-icon slot="start" name="thumb-up"></quiet-icon> </quiet-button>
You can insert more than one item into a named slot. For example, this card has a footer
slot
that houses two buttons. The text node remains in the default slot.
<quiet-card style="max-width: 340px;"> The sleepy tabby cat stretched languidly in the sunbeam, her whiskers twitching as she dreamed of chasing mice and climbing curtains with boundless energy. <quiet-button slot="footer" variant="primary">Primary</quiet-button> <quiet-button slot="footer">Default</quiet-button> </quiet-card>
Events Jump to heading
Many components emit custom events when certain things happen. For example, a
<quiet-tab-list>
emits an event called
quiet-tab-shown
when a tab is shown. You can listen for custom events the same way you listen
for native events.
<quiet-text-field></quiet-text-field> <script> const textField = document.querySelector('quiet-text-field'); textField.addEventListener('quiet-input', event => { // The text field has received input console.log(event); }); </script>
You can also listen to native events on custom elements. However, it's important to understand that native events occur inside the component's shadow DOM and are retargeted to the host, so they may not always work the way you expect — particularly in complex components. If a custom event is available, it's always better to use it over a native one.
Event bubbling is a common pitfall. In the same way native HTML elements all dispatch a
click
event, Quiet components may dispatch custom events that aren't unique to the component.
Learn more about custom event bubbling.
Some custom events contain a detail
property with additional information. You can access this
information through the detail
property of the event.
document.addEventListener('quiet-discovery-complete', event => { console.log(event.detail); // result { registered: [], unknown: [] } });
The information contained in an event's detail
property is described in the respective
component's documentation.
Methods Jump to heading
Some components have methods you can call. To call a method, you'll first need to obtain a reference to the
element. For example, you can programmatically set focus to a button by calling its
focus()
method.
const button = document.querySelector('quiet-button'); button.focus();
Not all components have methods. Those that do will be listed in the respective component's documentation.
Styling custom elements Jump to heading
Custom elements commonly use a platform feature called shadow DOM to encapsulate behaviors and styles. Their internals are rendered in a separate document fragment, which prevents most styles from leaking in from and out to the main document.
Speaking of internals…it's important to look at the documentation for each component to understand its anatomy before applying any CSS. A component often requires styles to target a specific part inside of it rather than the element itself.
By design, some styles, such as font size and family, will inherit through the shadow DOM. However, the majority of them do not. Thus, you cannot use standard CSS selectors to target a custom element's internals. Instead, you need to use one of the following APIs.
Please read this section thoroughly if you've never styled custom elements before. It's a common frustration because it involves CSS features you may not have used before.
CSS custom properties Jump to heading
CSS custom properties, often referred to as CSS variables, are unique in that their values will inherit through the shadow root. Custom properties are typically used when a value needs to be reused more than once within the custom element, or when they map to a property that isn't necessarily intuitive.
For example, <quiet-spinner>
renders an animated
SVG in its shadow root. The color of the indicator is applied to a <circle>
element
within the SVG, but the implementation detail shouldn't matter to a user who just wants to make the spinner
pink.
For this reason, this spinner exposes a custom property called --indicator-color
that you can
set to change the color. You can set a custom property just like any other CSS property.
<quiet-spinner style="--indicator-color: deeppink;"></quiet-spinner>
You can also set custom properties inside a stylesheet or a <style>
element.
quiet-spinner { --indicator-color: deeppink; }
Abstractions like this allow custom element authors to change or even replace a custom element's internals without modifying its public API. This means less breaking changes for you later on!
Not all components expose custom properties. Refer to the documentation to see which custom properties a component has.
CSS parts Jump to heading
Many components expose parts inside the shadow DOM that you can target with CSS. Unlike custom properties, which only modify individual properties, a part gives you complete control over the exposed element's styles.
Use the
::part()
selector to target a specific part in your CSS. This example applies a striped background image to the
progress bar's indicator.
<quiet-progress id="custom-progress-bg" label="Progress" value="50"></quiet-progress> <style> #custom-progress-bg::part(indicator) { background-image: linear-gradient(-45deg, #fff3 25%, transparent 25%, transparent 50%, #fff3 50%, #fff3 75%, transparent 75%, transparent); } </style>
One caveat of parts is you can't select children, siblings, or structural pseudo elements of a part such as
:first-child
, :last-child
, :first-of-type
, etc. This would break the
principal of encapsulation and it tends to be a source of frustration for users.
You can, however, select non-structural pseudo elements such as ::before
and
::after
, as well as pseudo classes like :hover
, :active
,
:focus
. If you're on a device that support hovering, hover over the indicator and observe the
background color change.
<quiet-progress id="custom-progress-hv" label="Progress" value="50"></quiet-progress> <style> #custom-progress-hv::part(indicator):hover { background-color: deeppink; } </style>
Another caveat of parts involves animation. When you target an element with ::part()
, you're
changing styles inside the shadow DOM. However, animations require keyframes to exist in the same document,
and it's not currently possible to define keyframes from outside the shadow DOM. You can, however, apply
animations to the host element itself.
Not all components expose parts. Refer to the documentation to see which parts a component has.
Custom states Jump to heading
Some components have custom states, a newer API that let's you target a custom element when it's in a particular state, such as active or disabled.
Toggle buttons, for example, have a
toggled
state you can use to target buttons when they're activated. Try clicking the button
below and observe its styles.
<quiet-button id="toggle-button" toggle="off">Toggle me</quiet-button> <style> #toggle-button:state(toggled) { outline: dashed 4px deeppink; outline-offset: 4px; } </style>
Not all components have custom states. Refer to the documentation to see which custom states a component has.