Developing a Component
This guide explains the techniques involved in the ongoing development a Spectrum control as a
The components in spectrum-web-components are based on the CSS definitions in spectrum-css
project typically specifies most, if not all, of the presentation details.
What is a web component?
According to
... a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. Custom components and widgets build on the Web Component standards, will work across modern browsers, and can be used with any JavaScript library or framework that works with HTML.
In order to add a new component to this library, you will need to develop a working knowledge of the following technologies:
Spectrum CSS : A CSS implementation of the Spectrum design languageWeb Components : Standards based method for adding new HTML tags to a browserShadow DOM : The part of the Web Component spec that allows for encapsulation of component styles and child nodeslit-element : A simple base class for creating fast, lightweight web componentsCSS custom properties : CSS variables that can be used throughout a documentTypescript : A typesafe variant of JavaScript
Setting up the styling
The most complicated part of implementing a Spectrum web component is getting the styles set up correctly. The
The CSS from the
The first step is to create a directory and a spectrum-config.js
file for your new component. This config file contains information about the structure of the web component in relation to the Spectrum CSS classes.
Below is a fragment of the spectrum-config.js
file for sp-button
const config = { conversions: [ { inPackage: '@spectrum-css/button', outPackage: 'button', fileName: 'button', excludeByComponents: [builder.element('a')], components: [ converter.classToHost(), converter.classToAttribute('spectrum-Button--quiet'), converter.classToAttribute('is-disabled', 'disabled'), converter.pseudoToAttribute('disabled', 'disabled'), ...converter.enumerateAttributes( [ ['spectrum-Button--sizeS', 's'], ['spectrum-Button--sizeM', 'm'], ['spectrum-Button--sizeL', 'l'], ['spectrum-Button--sizeXL', 'xl'], ], 'size' ), converter.classToId('spectrum-Button-label'), converter.classToSlotted('spectrum-Icon', 'icon'), { find: [ builder.class('spectrum-Icon'), builder.combinator('+'), builder.class('spectrum-Button-label'), ], replace: [ { replace: builder.attribute('name', 'icon', 'equal'), hoist: false, }, builder.combinator('+'), builder.id('label'), ], }, { hoist: false, find: builder.pseudoClass('empty'), replace: builder.attribute('hidden'), }, ], }, ], };
If we wanted to create a button component using this config file, the steps would be as follows:
- Make the directory
src/components/button
- In that new directory, create a
file with the above contentsspectrum-config.js
- Run the command
yarn process-spectrum
to create theCSS file
When you do the above, the spectrum-css
Structure of a Spectrum Web Component
If you look at an sp-button
in the Chrome developer tools, you will see a DOM structure that looks like this.
▼<sp-button tabindex="0" variant="accent"> ▼ #shadow-root (open) ▼ <button id="button" tabindex="0"> ▼ <div id="label> ▼ <slot> ↳ #text </slot> </div> </button> "Click Me" </sp-button>
If anything here looks unfamiliar, it is probably a good time to do some reading about
You can compare this markup with the spectrum-css
documentation
Host Class Mapping
We need to determine what the main CSS class is for our component in the original spectrum-css
. In the case of sp-button
, we can see that the top-level class is .Spectrum-Button
. We then need to determine where we want that CSS to be applied. In many cases, you will want that CSS to be applied to the actual web component via the :host
selector. That is the default behaviour of the conversion script. In this case, we wanted to preserve all of the default behaviour of the button
element in HTML. So, we want the main CSS to be applied to our <button>
instead. If you look at the host
definition in spectrum-config.js
shadowSelector
option. That tells the script to move all of the CSS for .Spectrum-Button
to the #button
element in the shadow DOM.
host: { selector: '.spectrum-Button', shadowSelector: '#button', },
Shadow DOM Structure
The next step is to fill out the remaining structure of the shadow DOM portion of the component. Note that, in the shadow DOM, we are using ids instead of long class names. We can do that because the namespace of each instance of our web component has it's own DOM scope. So, there can never be an id name collision.
Typically, you will reference the spectrum-css
In the case of sp-checkbox
, we turn this sample DOM code:
<label class="spectrum-Checkbox"> <input type="checkbox" class="spectrum-Checkbox-input" id="checkbox-0"> <span class="spectrum-Checkbox-box"> <svg class="spectrum-Icon spectrum-UIIcon-CheckmarkSmall spectrum-Checkbox-checkmark" focusable="false" aria-hidden="true"> <use xlink:href="#spectrum-css-icon-CheckmarkSmall" /> </svg> <svg class="spectrum-Icon spectrum-UIIcon-DashSmall spectrum-Checkbox-partialCheckmark" focusable="false" aria-hidden="true"> <use xlink:href="#spectrum-css-icon-DashSmall" /> </svg> </span> <span class="spectrum-Checkbox-label">Checkbox</span> </label>
into this code in our component's render method (actually implementation is slightly different):
return html` <label id="root"> <input id="input" type="checkbox" ?checked=${this.checked} @change=${this.handleChange} <span id="box"> <sp-icon id="checkmark" size="s" name="ui:CheckmarkSmall" aria-hidden="true" ></sp-icon> <sp-icon id="partialCheckmark" size="s" name="ui:DashSmall" aria-hidden="true" ></sp-icon> </span> <span id="label"><slot></slot></span> </label> `;
You will notice that many of the spectrum-css
classes are mapped to ids in the web component. For example, .spectrum-Checkbox-input
and .spectrum-Checkbox-box
become #input
and #box
. Those transformations are described in the ids
section of the spectrum-config.js
file
ids: [ { selector: '.spectrum-Checkbox-input', name: 'input', }, { selector: '.spectrum-Checkbox-box', name: 'box', }, { selector: '.spectrum-Checkbox-checkmark', name: 'checkmark', }, { selector: '.spectrum-Checkbox-partialCheckmark', name: 'partialCheckmark', }, { selector: '.spectrum-Checkbox-label', name: 'label', }, ],
Properties and Attributes
Most of our controls have options that affect how they are rendered. For example, Spectrum supports a number of different kinds of buttons (e.g primary, secondary or call-to-action). spectrum-css
supports these visual styles using CSS classes. In web components, we typically support these options using attributes/properties on the component. For example, here is a call-to-action style button.
<sp-button variant="accent">CTA</sp-button>
We could conditionally add CSS classes to the elements of the shadow DOM during rendering, but it is much easier to just let the attributes on the DOM node drive the styling directly. In order to facilitate that, the spectrum-config.js
file lets you specify how to map the various spectrum-css
classes to CSS that is based on the attributes on the :host
of the web component
attributes: [ { type: 'boolean', selector: '.spectrum-Button--quiet', }, { type: 'boolean', selector: ':disabled', }, { type: 'enum', name: 'variant', values: [ '.spectrum-Button--cta', '.spectrum-Button--primary', '.spectrum-Button--secondary', { name: 'negative', selector: '.spectrum-Button--warning', }, '.spectrum-Button--overBackground', '.spectrum-Button--secondary', ], }, ],
We support two different kinds of attributes, booleans and enums. Booleans are turned on or off by the presence or absence of the attribute. Enum attributes must be set to one of the allowed values. The host:
element directly
Class to Class Mapping
In some cases, you will need to retain the spectrum-css
classes as classes. An example of that is when you need to apply CSS rules to multiple items in the shadow DOM. In that case, we simply map class names to shorter classnames. There is an
classes: [ { selector: '.spectrum-Slider-track', name: 'track', }, ],
Slots
spectrum-css
for a component sometimes contains rules for laying out the child content. There is a slots
sectionspectrum-config.js
file for mapping those rules to the slotted content.
slots: [ { name: 'icon', selector: '.spectrum-Icon', }, ],
The above section tells our CSS processor to map CSS for the .spectrum-Icon
class to the content that is being hosted in the icon
Coding the Component
All of the spectrum-web-components
are written using the
We have a working specification for the APIs for each of the Spectrum components. If you file an issue for the component that you want to implement, we can provide the necessary specifications for it.
Documenting the component
The documentation for each component in the documentation site is adopted from the README.md
in said package. The pages are written in
To run the local documentation server, use the command:
yarn docs:start
The documentation automatically extracts the properties and attributes from the source code. You should document your component using the
Documentation standards
Each component's packages/_componentname_/README.md
. These files must meet our standards below:
- Heading structure must communicate the organization of the docs page. See W3C WAI's Tutorial on
Headings . - Main headings (level 2 and 3) should be consistent from component to component. See W3C WAI's
Understanding SC 3.2: Predictable and theDocumentation structure section below. - All examples code must be accessible.
- The example code must show the component with enough context to demonstrate how to use it with other elements in an accessible way. See how the examples in
show the component used with field elements.packages/help-text/README.md
- The "Accessibility" section contains tips on how to use the component accessibly. See the Accessibility section of
.packages/picker/README.md
- The "Accessibility" section contains notes on any accessibility considerations that affect the component's development. See the notes on cross-root ARIA in Accessibility section of
.packages/help-text/README.md
Documentation structure
Our component documentation should follow the structure below. See packages/menu/README.md
packages/help-text/README.md
## Overview ### Usage <-- Information on how to import the component. --> ### Anatomy <-- Information and examples about options (eg., labels, icons, etc.) of the component. --> ### Options <-- Information and examples about options (eg., sizes, variants, states, etc.) of the component. --> ### Behaviors <-- Information and examples about values, events, and methods of the component. --> ### Accessibility <-- Tips and examples on how to use the component accessibly with notes how accessibility considerations for how the component was developed. -->
Working with Storybook
We use
To run Storybook, use the command:
yarn storybook