Why web components?
Web developers finally have a way to create their own HTML elements and use them in any framework they want! This is the technology that Quartz is built on.
Thanks to the popularity of frameworks such as Angular, Vue, and React, component-driven development has become a part of our everyday lives. Components help us encapsulate styles and behaviors into reusable building blocks. They make a lot of sense in terms of design, development, and testing.
Unfortunately, framework-specific components fail us in several ways:
- You can only use them in the framework they're designed for
- Their lifespan is limited to that of the framework's
- New frameworks/versions can lead to breaking changes, requiring substantial effort to update components
Web components solve these problems. They're supported by all modern browsers, they're framework-agnostic, and they're part of the standard, so we know they'll be supported for many years to come 👍.
Using web components
Quartz components are just regular HTML elements, or custom elements to be precise. You can use them like any other element. Each component has detailed documentation that describes its full API, including properties, events, methods, and more.
If you're new to custom elements, often referred to as "web components", this page section will familiarize you with how to use them.
Attributes & Properties
Many components have properties that can be set using attributes. For example,
buttons accept a size
attribute that maps to the size
property which
dictates the button's size:
<qds-button size="small" text="Click me"></qds-button>
Some properties are boolean, so they only have true/false values. To activate a boolean property, add the corresponding attribute without a value:
<qds-button disabled text="Click me"></qds-button>
Refer to a component's documentation for a complete list of its properties.
Events
You can listen for standard events such as click
, mouseover
, etc. as you
normally would. However, it's important to note that many events emitted within
a component's shadow root will be retargeted to the host element. This may
result in, for example, multiple click
handlers executing even if the user
clicks just once. Furthermore, event.target
will point to the host element,
making things even more confusing.
As a result, you should almost always listen for custom events instead. For
example, instead of listening to click
to determine when a <qds-checkbox>
gets toggled, listen to qdsChange
:
<qds-checkbox text="Check me"></qds-checkbox>
<script>
const checkbox = document.querySelector('qds-checkbox')
checkbox.addEventListener('qdsChange', (event) => {
console.log(event.target.checked ? 'checked' : 'not checked')
})
</script>
All custom events are prefixed with qds
to prevent collisions with standard
events and other libraries. Refer to a component's documentation for a complete
list of its custom events.
Methods
Some components have methods you can call to trigger various behaviors. For
example, you can set focus on a Quartz button using the focus()
method:
<qds-button></qds-button>
<script>
const button = document.querySelector('qds-button')
button.focus()
</script>
Refer to a component's documentation for a complete list of its methods and their arguments.
Slots
Some components use slots to accept content inside of them. The most common slot
is the default slot, which includes any content inside the component that
doesn't have a slot
attribute.
For example, a title's default slot is used to populate its text:
<qds-title>Title text</qds-title>
Refer to a component's documentation for a complete list of available slots.
Don't use self-closing tags
Custom elements cannot have self-closing tags. Similar to <script>
and
<textarea>
, you must always include the full closing tag.
<!-- Don't do this -->
<qds-input />
<!-- Always do this -->
<qds-input></qds-input>
Differences from native elements
You might expect similarly named elements to share the same API as native elements. Quartz makes its best effort to do this, but it will not always be able to. Quartz components are not designed to be one-to-one replacements for their HTML counterparts.
For example, <input>
and <qds-input>
both have a size
attribute, but it
does different things. The former controls the width of the field based on the
type of the input and the latter controls the input's height.
Waiting for components to load
Web components are registered with JavaScript, so depending on how and when you load Quartz, you may notice a Flash of Undefined Custom Elements (FOUCE) when the page loads. There are a couple of ways to prevent this, both of which are described in the linked article.
One option is to use the :defined
CSS pseudo-class to "hide" custom
elements that haven't been registered yet. You can scope it to specific tags or
you hide all undefined custom elements as shown below:
:not(:defined) {
visibility: hidden;
}
As soon as a custom element is registered, it will immediately appear with all
of its styles, effectively eliminating FOUCE. Note the use of
visibility: hidden
instead of display: none
to reduce shifting as elements
are registered. The drawback to this approach is that custom elements can
potentially appear one by one instead of all at the same time.
Another option is to use customElements.whenDefined()
, which returns a
promise that resolves when the specified element gets registered. You'll
probably want to use it with Promise.allSettled()
in case an element fails
to load for some reason.
A clever way to use this method is to hide the <body>
with opacity: 0
and
add a class that fades it in as soon as all your custom elements are defined:
<style>
body {
opacity: 0;
}
body.ready {
opacity: 1;
transition: 0.25s opacity;
}
</style>
<script type="module">
await Promise.allSettled([
customElements.whenDefined('qds-button'),
customElements.whenDefined('qds-input'),
customElements.whenDefined('qds-title'),
])
// Button, input, and title are registered now! Add
// the `ready` class so the UI fades in.
document.body.classList.add('ready')
</script>
Code Completion
Visual Studio Code
Quartz ships with a file called vscode.html-custom-data.json
that can be used
to describe its custom elements to Visual Studio Code. This enables code
completion for Quartz components (also known as "code hinting" or
"IntelliSense"). To enable it, you need to tell Visual Studio Code where the
file is.
- Install Quartz locally
- If it doesn't already exist, create a directory called
.vscode
at the root of your project - If it doesn't already exist, create a file inside that folder called
settings.json
- Add the following to the file:
{
"html.customData": [
"./node_modules/@quartzds/core/dist/vscode.html-custom-data.json"
]
}
If settings.json
already exists, you only need to add the above line to the
root of the object.
Other Editors
Most popular editors support custom code completion with a bit of configuration. Please submit a feature request for your editor of choice. PRs are also welcome!