Skip to main content

Using Components #

Duet makes it easy to implement and use its components across any framework or no framework at all. We accomplish this by using standardized web platform APIs and Web Components.

Integrating Duet’s components to a project without a JavaScript framework is straight forward. If you’re working on a simple HTML page, you can start using the components immediately by adding these script tags to the <head>:

<script type="module" src="https://cdn.jsdelivr.net/npm/@duetds/components@latest/lib/duet/duet.esm.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@duetds/components@latest/lib/duet/duet.js"></script>

Once included, components can be used in your markup like any other regular HTML elements:

<duet-input label="Your name" placeholder="John Doe"></duet-input>
<duet-button variation="primary">Send</duet-button>

To load the correct webfonts as well, add either one of these tags to the <head>, depending on which theme you’re working on:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/fonts@latest/lib/localtapiola.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/fonts@latest/lib/turva.css" />

Please note: While including components directly from JSDelivr is convenient for quick prototyping and building simple websites, we recommend that for production usage you install the packages using NPM and host the different parts yourself. Below you can find instructions on how to do that with and without different JavaScript frameworks.


Installation #

Before moving further, please make sure you have Node.js installed on your machine. You can install the latest version through their website. If you’re planning on using Duet Components in a project that doesn’t yet use Node Package Manager, you’ll have to first create a package.json file. To do so, run npm init and follow the steps provided.

Once finished, you can install components by running one of:

# WEB COMPONENTS for Angular, Ember, Vue.js, and Vanilla JS:
# (The recommended way if you’re not using React)
npm install @duetds/components
# REACT COMPONENTS (experimental):
npm install @duetds/react
# ANGULAR COMPONENTS (experimental):
# (For stable releases, use @duetds/components instead)
npm install @duetds/angular

While components work without these packages, it’s also recommended to install @duetds/css and @duetds/fonts for the best user experience:

# CSS FRAMEWORK (platform independent):
npm install @duetds/css

# WEBFONTS (platform independent):
npm install @duetds/fonts

Usage with basic HTML #

Once you’ve installed @duetds/components package into your project, it’s recommended to create a copy task that copies the component library from node_modules to a location you’ve speficied. One such tool that can do this is NCP. You can install ncp by running:

npm install ncp

Once installed, add a script to your package.json that copies the component library from Duet’s package into a location you’ve specified:

"scripts": {
"copy:components": "ncp node_modules/@duetds/components/lib src/SPECIFY_PATH"
}

You can call this script while starting up your app to make sure you’ve always got the latest component library copied over. If you’re using an UNIX-like environment, you can use & as the separator:

"start": "copy:components & dev"

Otherwise, if you need a cross-platform solution, use npm-run-all module:

"start": "npm-run-all copy:components dev"

Once you have a copy task in place and have copied the component library over, you can put script tags similar to these in the <head> of your index.html:

<script type="module" src="SPECIFY_YOUR_PATH/duet.esm.js"></script>
<script nomodule src="SPECIFY_YOUR_PATH/duet.js"></script>

Once included, components can be used in your basic HTML markup as in the following example:

<duet-button variation="primary">Send</duet-button>

Usage with Angular #

Using @duetds/components within an Angular CLI project is a two-step process. To get started, you need to:

  1. Include the CUSTOM_ELEMENTS_SCHEMA in the modules that use the components.
  2. Call defineCustomElements(window) from main.ts (or other appropriate place).

1. Including the Custom Elements Schema #

Including the CUSTOM_ELEMENTS_SCHEMA in the module allows the use of the web components in the HTML markup without the compiler producing errors. Here is a simplified example of adding it to AppModule:

// ...
// Import custom elements schema
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@NgModule({
// ...
// Add custom elements schema to NgModule
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

The CUSTOM_ELEMENTS_SCHEMA needs to be included in any module that uses custom elements.

2. Calling defineCustomElements #

Duet Components include a main function that is used to load the components. That function is called defineCustomElements() and it needs to be called once during the bootstrapping of your application. One convenient place to do this is in main.ts as such:

// Import Duet Web Components
import { defineCustomElements } from "@duetds/components/lib/loader";
// ...
// Register Duet Web Components
defineCustomElements(window);

Once included, components can be used in your HTML markup as in the following example:

<duet-button variation="primary">Send</duet-button>

For more concrete usage examples see the templates section and individual component examples in the documentation.`

Edge and IE11 polyfills #

If you want your custom elements to be able to work on older browser, you should add the applyPolyfills() that surrond the defineCustomElements() function.

import { applyPolyfills, defineCustomElements } from "@duetds/components/lib/loader";
// ...
applyPolyfills().then(() => {
defineCustomElements(window)
})

Accessing using ViewChild and ViewChildren #

Once included, components could also be referenced in your code using ViewChild and ViewChildren as shown in the Stencil.js documentation.

Angular wrapper for Web Components #

In addition to our Core Web Component library, we have an Angular wrapper that is currently in beta testing. This means that the support is experimental at the moment and subject to change. If you want to help us test it you can install the below package instead of @duetds/components:

npm install @duetds/angular

Once you’ve installed the package you can import Duet Angular components in your application:

import { DuetComponents } from "@duetds/angular";

@NgModule({
//...
imports: [
DuetComponents
],
//...
})
export class AppModule { }

Usage with Vue.js #

To integrate @duetds/components into a Vue.js application, edit src/main.js to include:

// Import Duet Web Components
import { applyPolyfills, defineCustomElements } from "@duetds/components/lib/loader";
// ...
// configure Vue.js to ignore Duet Web Components
Vue.config.ignoredElements = [/duet-\w*/];
// Register Duet Web Components
applyPolyfills().then(() => {
defineCustomElements(window);
});

new Vue({
render: h => h(App)
}).$mount("#app");

Note that in the above scenario applyPolyfills is only needed if you are targeting Edge or IE11. Once included, components can be used in your HTML markup as in the following example:

<duet-button variation="primary">Send</duet-button>

For more concrete usage examples see the templates section and individual component examples in the documentation.`


Usage with React #

With an application built using the create-react-app script you can directly import the needed components from @duetds/react, which is a React specific wrapper created for Duet’s Web Components:

import React, { Component } from "react";
import { DuetButton } from "@duetds/react/lib";

export class ReactExample extends Component {
handleEvent(ev) {
ev.preventDefault()
// Perform an action
}
render() {
return (
<DuetButton variation="primary" onClick={handleEvent}>
Click me
</DuetButton>
);
}
}

React wrapper for @duetds/components includes two key differences to our core components bundle that you need to take into account:

  1. Component names start with an uppercase letter and use CamelCase format instead of kebab-case. For example <DuetNav> or <DuetButton>.

  2. All events start with on. So if you consider for example the navigation component that has custom duetChange event, that translates to onDuetChange when using Duet’s React library:

import React, { Component } from "react";
import { DuetNav } from "@duetds/react/lib";

export class ReactExample extends Component {
handleEvent(ev) {
const event = ev.detail.originalEvent
event.preventDefault()
// Perform an action
}
render() {
return (
<DuetNav
current-href="/"
language="fi"
region="Pääkaupunkiseutu"
contact="Ota yhteyttä"
onDuetChange={handleEvent}
items={[
{ label: "Etusivu", href: "/" },
{ label: "Vakuutukset", href: "#" },
{ label: "Vahinkoasiat", href: "#" },
{ label: "Säästöt ja sijoitukset", href: "#" },
{ label: "Laskut", href: "#", badge: true },
{ label: "Viestit", href: "#" }
]}
>

</DuetNav>
);
}
}

Usage with Gatsby #

While Duet’s components support prerendering/SSR in Stencil.js based applications and websites, we don’t yet offer full support for SSR in other frameworks like Gatsby. This is on our roadmap though, but before it’s finished you can use the following workaround to make things work in Gatsby.

For applications created with gatsby-cli, the easiest way is to add the following into your Webpack configuration in gatsby-node.js file:

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /@duetds\/react/,
use: loaders.null(),
},
],
},
})
}
}

The above configuration will replace Duet’s module with a dummy module during the build stage. The next step is to wrap any Duet Components you wish to use inside the following check:

{typeof window !== "undefined" && DuetButton && <DuetButton/>}

This will make sure that Duet’s components are only being rendered in the browser environment. A more concrete example would look like this:

import React from "react"
import Layout from "../components/layout"
import { DuetButton } from "@duetds/react/lib"

const IndexPage = () => (
<Layout>
<p>A basic paragraph</p>
{typeof window !== "undefined" && DuetButton && (
<DuetButton variation="primary">Button from Duet</DuetButton>
)}
</Layout>
)

export default IndexPage

Usage with Ember #

Duet components can be easily integrated into Ember thanks to the ember-cli-stencil addon that handles:

Start by installing the Ember addon:

ember install ember-cli-stencil

When you build your application, Stencil collections in your dependencies will be automatically discovered and pulled into your application. For more information, see ember-cli-stencil documentation.


Events #

We encourage the use of DOM events, but additionally provide custom events to make handling of certain event types easier. All custom events are always documented in component’s own documentation page.

All form and interactive components in Duet provide a custom event called duetChange. This custom event includes an object called detail which always provides the following meta data:

{
value: "new value of the component that changed",
component: "tag name of the component that triggered change"
}

Additionally, depending on the component type, this same detailobject can also include:

{
checked: "checked state of the component",
originalEvent: "original event so you can use e.g. preventDefault"
}

An example of the above is Input component that provides duetChange for detecting value changes inside the input field:

<duet-input label="My label" debounce="500"></duet-input>

<script>
// Select the above input component
var input = document.querySelector("duet-input")

// Listen for changes. Use debounce to adjust time to trigger.
input.addEventListener("duetChange", function(e) {
console.log("Change detected in input:", e.detail)
})
</script>

The console output for the above code looks like this:

Change detected in input: {
value: "string that user wrote in the input",
component: "duet-input"
}

In addition to form components, a few other component types also provide duetChange event that you can listen to. One example is Navigation that allows you to listen for navigation clicks inside the component throught duetChange:

<duet-header items="[
{ label: 'Etusivu', href: '#' },
{ label: 'Vakuutukset', href: '#' },
{ label: 'Vahinkoasiat', href: '#' },
{ label: 'Säästöt ja sijoitukset', href: '#' },
{ label: 'Laskut', href: '#', badge: true },
{ label: 'Viestit', href: '#' }
]"
>

</duet-header>

<script>
// Select the above navigation component
var nav = document.querySelector("duet-nav")

// Listen for change events inside the navigation.
// This gets triggered whenever a link or button in nav is clicked.
nav.addEventListener("duetChange", function(e) {
var event = e.detail.originalEvent
event.preventDefault()
console.log("Change detected in nav:", e.detail)
})
</script>

The console output for the above code looks like this:

Change detected in nav: {
originalEvent: {},
component: "duet-nav",
data: {
label: "Label of the item clicked",
href: "Href of the item clicked",
badge: "Whether the item has badge or not",
id: "Id attribute of item clicked"
}
}

Events in Angular #

example-component.html:

<duet-input #input
[value]="textValue"
(duetChange)="handleChange($event)">

</duet-input>

example-component.ts:

@Component({
selector: "example-component",
templateUrl: "./example-component.html",
})
export class ExampleComponent {
@ViewChild("input") input: HTMLDuetInputElement;
textValue = "Duet";
handleChange(event) {
// Value change event
}
}

The typing information for all components is included in Duet Components package. To find it, see @duetds/components/lib/types/components.d.ts.

Events in Vue #

<template>
<duet-input :value="textValue" v-on:duetChange="handleChange">
</duet-input>
</template>

<script>
export default {
name: "InputExample",
data: () => ({
textValue: "Duet",
}),
methods: {
handleChange(event) {
// value change event
},
}
}
</script>

Events in React #

import React, { Component } from "react";
import { DuetButton, DuetNav } from "@duetds/react/lib";

export class ReactExample extends Component {
handleClick(ev) {
// Perform an action
}
handleNavigation(ev) {
const event = ev.detail.originalEvent
event.preventDefault()
// Perform an action
}
render() {
return (
<div>
<DuetNav onDuetChange={handleNavigation}></DuetNav>
<DuetButton onClick={handleClick}>Button</DuetButton>
</div>
);
}
}

Theming #

Duet Design System’s components come in two themes: LocalTapiola and Turva. By default components use the LocalTapiola theme, but Duet allows you to define through a global CSS className or a local component property the currently active theme.

Changing theme globally #

To change a layout or the whole application to use Turva’s theme add .duet-theme-turva class to <html>:

<html class="duet-theme-turva">

Please note that there’s no dynamic watch task for the html class changes so the above only works if it’s in the html when the components are initialized.

Changing theme for a component #

To permanently set the theme of one or a few components, use the theme property:

<duet-component theme="turva"></duet-component>
<duet-component theme="default"></duet-component>

CSS Styles #

Duet’s components encapsulate their CSS styles and structure within Shadow DOM that prevents the injection of external styles. This means that you can’t override a component’s style with your own CSS. Instead, if the component looks or behaves incorrectly in your application and none of the provided component properties help, it’s most likely an issue in Duet and should be fixed here instead.

The components do however provide a way to disable their automatic space handling (margin and padding). All components come built-in with a property called margin that allows you to set it to either "auto" or "none".

When the margin property is set to "auto" the component will automatically take care of its surrounding space, but for cases when you want to remove the margins completely, you can use the "none" setting.

Additionally, some components come with more granular options for the margin property and also provide a property called padding that controls the inner spacing.

Finally, Duet also includes a component called Spacer that allows you to tweak the horizontal and vertical space between UI parts using Space Tokens.


Server Side Rendering #

Duet’s Web Components package includes a hydrate app that is a bundle of the same components, but compiled so that they can be hydrated on a NodeJS server and generate static HTML and CSS. To get started, import the hydrate app into your server’s code like so:

import hydrate from "@duetds/components/hydrate"

If you are using for example Eleventy, you could now add a transform into .eleventy.js configuration file that takes content as an input and processes it using Duet’s hydrate app:

eleventyConfig.addTransform("hydrate", async(content, outputPath) => {
if (process.env.ELEVENTY_ENV == "production") {
if (outputPath.endsWith(".html")) {
try {
const results = await hydrate.renderToString(content, {
clientHydrateAnnotations: true,
removeScripts: false,
removeUnusedStyles: false
})
return results.html
} catch (error) {
return error
}
}
}
return content
})

The above transform gives you server side rendered components that function without JavaScript. Please note that you need to separately pre-render the content for each theme you want to support.


Troubleshooting #

If you experience any issues while setting up the Components, please head over to the Support page for more guidelines and help.

Additionally, you might find Stencil.js’s documentation helpful as we use Stencil to compile Duet’s Web Components.


Terms of use #

Duet Design System is solely meant for building digital products and experiences for LocalTapiola and Turva. Please see the terms of use for full license details.