Skip to main content

Components Usage #

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 tags to the <head>:

<script type="module" src="https://cdn.duetds.com/api/components/8.7.1/lib/duet/duet.esm.js" integrity="sha384-c3hLghWHTPntUxndMlK4myD7d59stA2U0EvBp2rPb3ibtacAwh56geBpq6z7nXLn" crossorigin="anonymous"></script>
<script nomodule src="https://cdn.duetds.com/api/components/8.7.1/lib/duet/duet.js" integrity="sha384-H7RH4Ssj/LmElXa1VCHFSIjvtCSoLXMiPXG/KQZTS9EvYujTo7tWaj0V6/GGkY16" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.duetds.com/api/css/4.0.45/lib/duet.min.css" integrity="sha384-UoMJnpXiN8f7fKVnTzfKfyi7LzQlApQ+WTS9O3PXlYr6CO9yzou4glfsHV747f3v" crossorigin="anonymous" />

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.duetds.com/api/fonts/3.0.51/lib/localtapiola.css" integrity="sha384-5JYmtSD7nykpUvSmTW1CHMoBDkBZUpUmG0vuh+NUVtZag3F75Kr7+/JU3J7JV6Wq" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.duetds.com/api/fonts/3.0.51/lib/turva.css" integrity="sha384-hHdwZODJ+y2QoCpmMYq9dSnwexFN8FO9B9cVru7Y7iy2l3bXKpf/vNfPASXgfKWU" crossorigin="anonymous" />
For an up-to-date list of components included and their respective documentations, see Component status page.

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 HTML, Ember, Vue.js, and Vanilla JS:
npm install @duetds/components
# REACT COMPONENTS:
npm install @duetds/react

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 specified. One such tool that can do this is NCP. You can install ncp by running:

npm install ncp --save-dev

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>
Please note: we now favor the usage of Duet CDN over this approach. Scroll to the top of the page to find the correct script and link tags.
For more concrete usage examples see the templates section and individual component examples in the documentation.

Usage with Angular #

To get started, first install our Web Components package:

npm install @duetds/components

Before you can use the Web Components in Angular, you must import and add Angular’s CUSTOM_ELEMENTS_SCHEMA. This allows the use of Web Components in HTML markup, without the compiler producing errors. The CUSTOM_ELEMENTS_SCHEMA needs to be included in any module that uses custom elements. Typically, this can be added 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 final step is to load and register Duet’s components in the browser. @duetds/components includes a main function that handles this. 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 surround the defineCustomElements() function.

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

Angular example project #

You can find example Angular projects from our CLI tool. View the CLI tool documentation.


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 #

Before react 19 #

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, useState } from "react";
import { DuetButton } from "@duetds/react";

// as a class component
export class ReactExample extends Component {
state = {
clicks: 0
}

handleEvent = (ev) => {
ev.preventDefault()
this.setState(s => ({
clicks: s.clicks + 1
}))
}

render() {
return (
<DuetButton variation="primary" onClick={this.handleEvent}>
Click me: {this.state.clicks}
</DuetButton>
);
}
}

// or as a function component
export function ReactExample() {
const [clicks, setClicks] = useState(0);

function handleEvent (ev) {
ev.preventDefault()
setClicks(clicks => clicks + 1)
}

return (
<DuetButton variation="primary" onClick={handleEvent}>
Click me: {clicks}
</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 <DuetHeader> or <DuetButton>.

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

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

// as a class
export class ReactExample extends Component {
handleEvent = (ev) => {
const event = ev.detail.originalEvent
event.preventDefault()
// Perform an action
}

render() {
return (
<DuetHeader
current-href="/"
language="fi"
region="Pääkaupunkiseutu"
contact="Ota yhteyttä"
onDuetChange={this.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: "#" }
]}

/>

);
}
}

React 19 and higher #

React 19 introduced support for web components so there is no longer need to use @duetds/react wrapper.
With an application built using the create-react-app script you can directly import the needed components from @duetds/components.
You will also have to register Duet's component using defineCustomElements function provided by components lib:

import React, { Component, useState } from "react";
import { defineCustomElements } from '@duetds/components/lib/loader';

//this needs to be called only once, prefrably in your app root component
defineCustomElements();

// as a class component
export class ReactExample extends Component {
state = {
clicks: 0
}

handleEvent = (ev) => {
ev.preventDefault()
this.setState(s => ({
clicks: s.clicks + 1
}))
}

render() {
return (
<duet-button variation="primary" onClick={this.handleEvent}>
Click me: {this.state.clicks}
</duet-button>
);
}
}

// or as a function component
export function ReactExample() {
const [clicks, setClicks] = useState(0);

function handleEvent (ev) {
ev.preventDefault()
setClicks(clicks => clicks + 1)
}

return (
<duet-button variation="primary" onClick={handleEvent}>
Click me: {clicks}
</duet-button>
);

}

For event handling you need to use on{$eventName} so, for example, duetChange maps to onduetChange

import React, { Component } from "react";

// as a class
export class ReactExample extends Component {
handleEvent = (ev) => {
const event = ev.detail.originalEvent
event.preventDefault()
// Perform an action
}

render() {
return (
<duet-header
current-href="/"
language="fi"
region="Pääkaupunkiseutu"
contact="Ota yhteyttä"
onduetChange={this.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: "#" }
]}

/>

);
}
}

Usage with Next.js #

With an application built using Next.js, you need to import the components a little differently. This is because Duet ships ES Modules by default, but Next.js doesn’t fully support this. To import CommonJS instead, you can import the components using:

import { DuetHeader } from "@duetds/react/commonjs";

// or...

const { DuetHeader } = require("@duetds/react/commonjs");

Known issues #

When using DuetStepper and DuetStep, if you have any dynamic content that is added or removed from the DOM, you must add a wrapper div around all children of the step:

<DuetStepper>
<DuetStep>
<div>
{someCondition && <DuetInput label="Name" />}
</div>
</DuetStep>
</DuetStepper>

React example project #

We’ve created an example project to GitHub, demonstrating simple form handling. View the project.


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 detail object 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"
}

Detecting when a component has loaded #

The standard Web Components API includes a CustomElementsRegistry object that Duet uses to register its components. This object also includes a function called whenDefined which receives a name of a component and returns a promise. The promise will be resolved when the component is defined:

window.customElements.whenDefined("duet-button").then(() => {
console.log("Duet Button is defined!");
});

This can help you in e.g. situations where you want to use a component method immediately after a page load. If you additionally need to know when the component is also fully rendered, you could use componentOnReady() that our components provide:

await customElements.whenDefined("duet-button");
await document.querySelector("duet-button").componentOnReady();

Events in Angular #

You can use normal Angular event binding:

example-component.html:

<duet-input (duetChange)="handleChange($event)"></duet-input>
<duet-button (click)="handleClick()">Click me</duet-button>

example-component.ts:

@Component({
selector: "example-component",
templateUrl: "./example-component.html",
})
export class ExampleComponent {
textValue = "Duet";

handleChange(event) {
this.textValue = event.target.value
}

handleClick() {
console.log(this.textValue)
}
}

Notice that its also possible to use normal Web APIs to locate elements and add event listeners.

export class AppComponent {
@Component({
selector: "example-component",
templateUrl: "./example-component.html",
})
export class ExampleComponent {
textValue = "Duet";

constructor(private myElement: ElementRef) {}

ngOnInit() {
const element = this.myElement.nativeElement as Element
const button = element.querySelector("duet-button")
const input = element.querySelector("duet-input")
input.addEventListener("duetChange", this.handleChange)
button.addEventListener("click", this.handleClick)
}

ngOnDestroy() {
// remember to remove event listeners
}

handleChange = (e: CustomEvent<DuetInputEvent>) => {
this.textValue = e.detail.value
}

handleClick = () => {
console.log(this.textValue)
}
}

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

Angular's Reactive forms #

In order to use Angular's Reactive forms with web components its necessary to create appropriate value accessor directives that implement ControlValueAccessor. These directives act as a bridge between Angular forms API and web components. Duet doesn't provide or maintain these directives but there are example directives available in the latest CLI's Angular templates.

Events in Vue #

<template>
<duet-input
@click="onClick($event)"
@duetFocus="onFocus($event)"
@duetChange="onChange($event)"
:value.prop="textValue">

</duet-input>
</template>

<script>
export default {
name: "InputExample",
data: () => ({
textValue: "Duet",
}),
methods: {
onClick(event) {
// click event
},
onFocus(event) {
// focus event
},
onChange(event) {
// value change event
},
}
}
</script>

Events in React #

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

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>
<DuetHeader onDuetChange={this.handleNavigation}></DuetHeader>
<DuetButton onClick={this.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.

You can’t override component’s styles with your own CSS. Instead, if the component looks or behaves incorrectly and none of the provided props help, it’s an issue in Duet and should be fixed here instead.

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.

Edit