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

<script type="module" src="https://cdn.duetds.com/api/components/4.7.4/lib/duet/duet.esm.js" integrity="sha384-P4A6+AMluHP5nVjKIrl5tvN/QSaymtVwYZbVMCT0R1ND4ipiMgqeCxuguH1icn+E" crossorigin="anonymous"></script>
<script nomodule src="https://cdn.duetds.com/api/components/4.7.4/lib/duet/duet.js" integrity="sha384-qru0HruOE70bAbFK89SKUNI6k0YJstOrzRyAy6J6PJpwEUXowhskmnKjDZNV3Xn1" crossorigin="anonymous"></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.duetds.com/api/fonts/1.3.12/lib/localtapiola.css" integrity="sha384-5JYmtSD7nykpUvSmTW1CHMoBDkBZUpUmG0vuh+NUVtZag3F75Kr7+/JU3J7JV6Wq" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.duetds.com/api/fonts/1.3.12/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
# ANGULAR COMPONENTS:
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 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 #

The recommended way to use Duet’s components in Angular is via the @duetds/angular package. This package offers Angular-specific wrappers around Duet’s Web Components which have tight integration with Angular features like reactive forms. To install the package, run:

npm install @duetds/angular

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

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

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

The Duet Angular components offer integration with Angular’s reactive forms. You will likely want to import/configure this in your app module:

import { DuetComponents } from "@duetds/angular";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";

@NgModule({
//...
imports: [
DuetComponents,
FormsModule,
ReactiveFormsModule,
],
//...
})
export class AppModule { }
Angular specific version gives types for your components and you also get the ability to use ngmodel on inputs and other form fields.

Using Web Components in Angular #

It’s recommended to use the @duetds/angular package, but you can also use our Web Components directly. 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)
})

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.


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, 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: "#" }
]}

/>

);
}
}

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");

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 Header that allows you to listen for navigation clicks inside the component through duetChange:

<duet-header></duet-header>

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

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

The console output for the above code looks like this:

Change detected in nav: {
originalEvent: {},
component: "duet-header",
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 #

If you are using the @duetds/angular package, then you can use reactive forms for input components. Other events are bound as usual in Angular:

example-component.html:

<duet-input
#input
[formControl]="textValue">

</duet-input>
<duet-button (click)="handleClick()">Click me</duet-button>

example-component.ts:

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { DuetInput } from "@duetds/angular";

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

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

See the Angular reactive forms documentation for more information.

If you are using the @duetds/components package, you must wire up values and event handlers for inputs yourself:

example-component.html:

<duet-input
#input
[value]="textValue"
(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 {
@ViewChild("input") input: HTMLDuetInputElement;
textValue = "Duet";

handleChange(event) {
this.textValue = event.target.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.

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