Web ComponentsNew
Nord makes it effortless 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.
Web Components are a set of technologies that provide a standards based component model for the Web. They allow us to create reusable Custom Elements with their functionality encapsulated away from the rest of the code.
We’ve chosen to use Web Components because there is a strong requirement for Nord to be used in many different contexts and with varying technologies — from static HTML pages to server-rendered apps, through to single page applications authored with frameworks such as React and Vue. Web Components work great for Nord, because they:
- Are tech-agnostic instead of tech-specific
- Future proof our system with Web Standards
- Allow us to use any framework or no framework at all
- Provide great encapsulation for styles and functionality
Quick start
There are two necessary parts to using Nord’s components — the Web Components themselves, and the CSS Framework. Everything else is optional. The fastest way to get started is to include the following directly into your page with <script>
and <link>
tags:
<link rel="stylesheet" href="https://nordcdn.net/ds/css/3.3.1/nord.min.css" integrity="sha384-x2XdCI8Yog7KGRmrrGLegjFrrIYXEhGNxql/xEXdMoW5NkpEhlAkUHdQJxkL1vPg" crossorigin="anonymous" />
<script type="module" src="https://nordcdn.net/ds/components/3.19.0/index.js" integrity="sha384-Hxzh12RVoHTpU2FOGZp+/nJxzuDRDoLKCpOMahxaYjmeImNHJJFhconKw3OjRkDd" crossorigin="anonymous"></script>
With these, you now have access to all of Nord’s components! You can now for example add a button to your page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Nord Design System</title>
<link rel="stylesheet" href="https://nordcdn.net/ds/css/3.3.1/nord.min.css" integrity="sha384-x2XdCI8Yog7KGRmrrGLegjFrrIYXEhGNxql/xEXdMoW5NkpEhlAkUHdQJxkL1vPg" crossorigin="anonymous" />
<script type="module" src="https://nordcdn.net/ds/components/3.19.0/index.js" integrity="sha384-Hxzh12RVoHTpU2FOGZp+/nJxzuDRDoLKCpOMahxaYjmeImNHJJFhconKw3OjRkDd" crossorigin="anonymous"></script>
</head>
<body>
<nord-button variant="primary">Hello Nordie</nord-button>
</body>
</html>
Installation
We strive to support as many use-cases as possible for consuming Nord’s components. In its purest form, you can include our components and styles via <script>
and <link>
tags served from our CDN.
This approach is best for rapid prototyping, for use in static HTML pages, or for integrating into existing apps which do not have a JavaScript build step. For all other cases we recommend installing the packages locally via npm/yarn.
CDN installation
Include the following in the <head>
of your web page:
<link rel="stylesheet" href="https://nordcdn.net/ds/css/3.3.1/nord.min.css" integrity="sha384-x2XdCI8Yog7KGRmrrGLegjFrrIYXEhGNxql/xEXdMoW5NkpEhlAkUHdQJxkL1vPg" crossorigin="anonymous" />
<script type="module" src="https://nordcdn.net/ds/components/3.19.0/index.js" integrity="sha384-Hxzh12RVoHTpU2FOGZp+/nJxzuDRDoLKCpOMahxaYjmeImNHJJFhconKw3OjRkDd" crossorigin="anonymous"></script>
If working on a custom themed app, you should also include the theme after the CSS Framework:
<link rel="stylesheet" href="https://nordcdn.net/ds/css/3.3.1/nord.min.css" integrity="sha384-x2XdCI8Yog7KGRmrrGLegjFrrIYXEhGNxql/xEXdMoW5NkpEhlAkUHdQJxkL1vPg" crossorigin="anonymous" />
<link rel="stylesheet" href="https://nordcdn.net/ds/themes/8.1.1/nord-dark.css" integrity="sha384-4mfQkitA1YUssjHukrfVhopnhPw9eM2tX8Z05rZ/5NJRmDJN1fQp2gGfwydx2SzL" crossorigin="anonymous" />
<script type="module" src="https://nordcdn.net/ds/components/3.19.0/index.js" integrity="sha384-Hxzh12RVoHTpU2FOGZp+/nJxzuDRDoLKCpOMahxaYjmeImNHJJFhconKw3OjRkDd" crossorigin="anonymous"></script>
Local installation
Before moving further with the installation, 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 Nord’s Web Components in a project that doesn’t yet use Node Package Manager, you’ll have to first create a package.json file. To do this, run npm init
and follow the steps provided. Once finished, you can install Nord’s Web Components by following the instructions below.
Run in your project or website root directory (where package.json lives):
# With NPM
npm install @nordhealth/css @nordhealth/components --save
# With Yarn
yarn add @nordhealth/css @nordhealth/components
Required styles
It’s a requirement that you use our CSS framework alongside the Web Components. The CSS framework includes all our design tokens, a default theme for the components, as well as a number of helpful utility classes.
Outside of the components themselves, we recommend leveraging the included design tokens and utility classes as much as possible to achieve a consistent look and feel.
If working in Veterinary or Healthcare space, you will also need a specific theme from our themes package in your application:
# With NPM
npm install @nordhealth/themes --save
# With Yarn
yarn add @nordhealth/themes
Bundling
Bundling and transpiling are considered application concerns. We cannot anticipate all requirements, loading strategies, and so on. So we publish our components as ES modules, written for modern browsers. These can then be bundled, tree shaken, and optimized by individual applications.
To import all components:
import '@nordhealth/components'
To import specific components, which allows for optimal tree shaking:
import '@nordhealth/components/lib/Badge'
import '@nordhealth/components/lib/Button'
import '@nordhealth/components/lib/Input'
These imports automatically register the Web Components (side effects).
Browser support
Nord Design System is tested in the latest two versions of the following browsers. Our team addresses critical bug fixes in earlier versions based on their severity and impact. If you need to support IE11 or pre-Chromium Edge, this library isn’t for you.
Usage
Properties
In browsers, elements have attributes in HTML and properties in JavaScript. Our attributes are always in dash-case
, and the properties are in camelCase
.
All attributes have corresponding underlying properties, but not all properties have corresponding attributes. Since HTML only has two types of values, strings and booleans, any property which is not a string or a boolean cannot exist as an attribute. For instance, a function cannot be passed via an attribute, so it must be passed via JavaScript:
<nord-calendar></nord-calendar>
<script type="module">
const calendar = document.querySelector("nord-calendar")
// this could not be set via an attribute
calendar.isDateDisabled = function isWeekend(date) {
return date.getDay() === 0 || date.getDay() === 6
}
</script>
Boolean attributes are considered false
by omission, and true
by inclusion. For instance, to set the hideLabel
property of an input to true
, you include the hide-label
attribute without a value:
<!-- hideLabel property is set to true -->
<nord-input label="Name" hide-label></nord-input>
It’s recommended, as much as possible, to leverage attributes in HTML rather than properties in JavaScript.
Events
Components emit custom DOM events, which can be listened to via addEventListener
or whatever mechanism your framework of choice offers. It’s worth noting that all our events bubble.
Where our components are drop-in replacements for native elements we follow native event names. For instance, our Input and other form components, emit change
, input
, blur
, focus
events.
<nord-input id="events-example" label="Name"></nord-input>
<script type="module">
document.querySelector("#events-example").addEventListener("focus", e => {
console.log("focus", e.target)
})
</script>
Where there is no equivalent native event, we prefix our event names with nord-
. For instance, the Command Menu emits a nord-select
event when a command is chosen from the menu.
Methods
Some components offer methods which you can call to invoke behavior. Interactive components like Input, Button etc. offer focus()
, blur()
, and click()
methods, which behave like the native equivalents. Some components offer methods for which there is no native equivalent. For instance, Date Picker offers a show()
and hide()
method:
<nord-stack>
<nord-button id="methods-example-button">Open date picker</nord-button>
<nord-date-picker id="methods-example-picker" label="Date of birth"></nord-date-picker>
</nord-stack>
<script type="module">
const button = document.querySelector("#methods-example-button")
const picker = document.querySelector("#methods-example-picker")
button.addEventListener("click", () => {
picker.show()
})
</script>
If you are using a framework like React or Vue, you will need to get a reference to the DOM element to call the methods on. Please refer to the Vue or React docs for further information.
Slots
Web Components offer a feature called slots. As with events, properties and methods, slots form part of the public API of a component. Slots allow web components to receive content as children, and place the content into their internal DOM.
The most common slot is the default slot, which captures any content placed as a child of a component that does not have a slot
attribute. For instance, the Button component has a default slot:
<nord-button>Log in</nord-button>
In the above example the text “Log in” is placed into the Button’s default slot. You can also place elements inside the default slot, as can be seen here with the Card component:
Hello world
<nord-card>
<p>Hello world</p>
</nord-card>
Some components also offer named slots. Slot names are decided by the components themselves. Named slots are used to differentiate certain types of content, giving components control over style and position. Here we use the Button component’s start
slot to position an icon. Notice the slot="start"
attribute:
<nord-button variant="danger">
<nord-icon slot="start" name="interface-delete"></nord-icon>
Delete
</nord-button>
Slotted elements can be placed anywhere inside a component. The component will always place it in the correct place internally. Therefore, the below example will produce the exact same result as above, despite the order being changed:
<nord-button variant="danger">
Delete
<nord-icon slot="start" name="interface-delete"></nord-icon>
</nord-button>
CSS Properties
Our Web Components offer CSS Custom Properties to allow for fine grained control over their presentation. These aren’t offered as an alternative to properties, you should use properties whenever possible, but more as an “escape hatch” for when a component needs to meet an edge case.
Component custom properties are prefixed with --n-
followed by the name of the component and the property in question. For example consider this Banner component that we want to add a box-shadow
to:
<nord-stack>
<nord-banner variant="warning">
Default banner presentation
</nord-banner>
<nord-banner variant="warning" style="--n-banner-box-shadow: var(--n-box-shadow)">
Banner with box-shadow added
</nord-banner>
</nord-stack>
Custom properties are inheritable as well which means they can be applied to any parent, or even the :root
element, and their respective component will inherit the value(s).
<nord-stack class="custom-property-banners">
<nord-banner variant="info">
Example banner
</nord-banner>
<nord-banner variant="warning">
Example banner
</nord-banner>
<nord-banner variant="danger">
Example banner
</nord-banner>
</nord-stack>
<style>
.custom-property-banners {
--n-banner-box-shadow: var(--n-box-shadow);
}
</style>
You can see all the available custom properties for the Banner component on its respective docs page. Custom properties differ from component to component, refer to the component’s documentation for a complete list of available custom properties.
--_n-
. Please refrain from using private properties as they are always subject to change and intended for internal component use only.
Themes
We offer light and dark themes for components in addition to high contrast variants. Themes operate at a global level, rather than per component. The CSS Framework includes the light theme by default. For theme specific documentation, please see themes documentation. Make sure to try theme builder as well to get familiar with theming and how to customize it.
# Install themes using NPM
npm install @nordhealth/themes --save
# Install themes using Yarn
yarn add @nordhealth/themes
Waiting for components to load
Web Components are entirely self-contained (both styles and behavior), and can be loaded/defined asynchronously. Therefore a component may not be ready for use immediately.
When a component is loading, it should be hidden from view to avoid a Flash Of Un-styled Component (see FOUC). Our CSS framework will do this for you, as long as it is loaded before the components.
If you set a property/attribute on a custom element before it’s loaded, it will be applied correctly and will use the values once it’s loaded. However, you cannot call a method on a component before the component is loaded.
Most of the time this is not an issue, as you will be calling methods in event handlers etc, when the component has already loaded. In cases where you want to call a method as soon as possible, for example on page load, you need to wait for the component to be defined, using customElements.whenDefined
:
<nord-date-picker id="loading-example" label="Enter date"></nord-date-picker>
<script type="module">
const picker = document.querySelector("#loading-example")
// It's fine to set properties while components are still loading
picker.isDateDisabled = function isWeekend(date) {
return date.getDay() === 0 || date.getDay() === 6
}
// but if you want to immediately call a method,
// you should wait for the component to be defined
await customElements.whenDefined("nord-date-picker")
picker.focus({ preventScroll: true })
</script>
Code completion for VS Code
We generate TypeScript type definitions for all our Web Components, including the React wrappers. This will allow VS Code, and other editors, to offer code completion and type information for properties and methods, whether using TypeScript or not.
Form controls
All our input components raise change
events, and where it makes sense input
events. These are useful for client-side validation, or for gathering values in javascript-based apps like with Vue or React.
<form id="form-events-example">
<nord-stack>
<nord-input label="First name" name="firstName"></nord-input>
<nord-input label="Last name" name="lastName"></nord-input>
<nord-button variant="primary" type="submit">Submit</nord-button>
<output></output>
</nord-stack>
</form>
<script type="module">
const data = {}
const form = document.querySelector("#form-events-example")
const output = form.querySelector("output")
// Input events bubbling up from input components
form.addEventListener("input", e => {
data[e.target.name] = e.target.value
})
form.addEventListener("submit", e=> {
e.preventDefault()
output.innerHTML = `<code>${JSON.stringify(data)}</code>`
})
</script>
For server-rendered apps, our components hook into the browser’s formdata
event, which means that our components’ values will be submitted as part of POST/GET traditional form submissions, like native HTML elements:
<form id="form-submission-example">
<nord-stack>
<nord-input label="First name" name="firstName"></nord-input>
<nord-input label="Last name" name="lastName"></nord-input>
<nord-button variant="primary" type="submit">Submit</nord-button>
<output></output>
</nord-stack>
</form>
<script type="module">
const form = document.querySelector("#form-submission-example")
const output = form.querySelector("output")
const url = new URL(location.href)
if (url.searchParams) {
output.innerHTML = `<code>${url.searchParams}</code>`
}
</script>
The browser’s built-in FormData
object can also be used to get all values from a form via JavaScript:
<form id="form-data-example">
<nord-stack>
<nord-input label="First name" name="firstName"></nord-input>
<nord-input label="Last name" name="lastName"></nord-input>
<nord-button variant="primary" type="submit">Submit</nord-button>
<output></output>
</nord-stack>
</form>
<script type="module">
const form = document.querySelector("#form-data-example")
const output = form.querySelector("output")
form.addEventListener("submit", e => {
e.preventDefault()
const data = new FormData(form)
// This is an example, refer to MDN for more info about FormData.
output.innerHTML = `<code>${JSON.stringify(Object.fromEntries(data))}</code>`
})
</script>
Localization
Nord provides a lightweight solution for localizing its components. Not all components need localizing, as for the most part snippets of text are set per instance. For example, the label on an Input will likely be changed every time you use it. Those cases are an app-level concern.
However, some components have text which has no reason to change per instance. For example, the usage instructions in the footer of the Command menu, or the accessible labels for next/previous month buttons of the Calendar.
For the latter cases, we provide a localization mechanism. It is not possible or feasible for Nord to anticipate every locale in which the components might be consumed, so we rely on individual teams and applications to translate and configure localization. To ensure Nord components work out of the box, we bundle translations for en-US
.
React
Whilst React supports Web Components, they are very awkward to use as-is. For this reason, we provide React-specific wrapper components in the package @nordhealth/react
. This will allow you to use the Nord components as you would any other React component.
A planned future release of React will greatly improve support for Web Components, which will make the wrapper components unnecessary. But until then, we recommend their use.
Installation
Install with npm:
npm install @nordhealth/react @nordhealth/css --save
or yarn:
yarn add @nordhealth/react @nordhealth/css
Usage
You must import @nordhealth/css
once in your main entry point, then import components from @nordhealth/react
wherever they are needed:
import { useState } from "react"
import { createRoot } from "react-dom/client";
import "@nordhealth/css"
import { Button } from "@nordhealth/react"
function App() {
const [count, setCount] = useState(0)
return (
<Button
variant="primary"
onClick={() => setCount(count + 1)}
>
Clicks: {count}
</Button>
)
}
const root = createRoot(document.querySelector("#app"));
root.render(<App />);
The react wrapper components forward their refs, so that you can get access to the underlying web component instance with useRef
. The @nordhealth/react
package also exports types for all the web components for convenience.
The following example shows how to use refs in a typescript application:
import { useRef } from "react"
import { createRoot } from "react-dom/client";
import "@nordhealth/css"
import { Button, Input } from "@nordhealth/react"
import type { InputWC } from "@nordhealth/react"
function App() {
const inputRef = useRef<InputWC>(null)
function handleClick() {
inputRef.current?.focus()
}
return (
<Button variant="primary" onClick={handleClick}>
Focus input
</Button>
<Input label="Name" ref={ref}>
)
}
const root = createRoot(document.querySelector("#app"));
root.render(<App />);
Usage with Next.js
Currently there is some friction when using our components in a Next.js app. It is possible to overcome these issues yourself, but to make the process easier we offer specialized integration for Next.js.
Instead of importing components as above, you should import components from our Next.js specific integration:
// instead of:
import { Button } from "@nordhealth/react"
// do this:
import { Button } from "@nordhealth/react/lib/next.js"
Vue
Vue has excellent support for Web Components out of the box, it only requires a little configuration.
Installation
Install with npm:
npm install @nordhealth/components @nordhealth/css --save
or yarn:
yarn add @nordhealth/components @nordhealth/css
Configuration
Vue needs to be configured to recognize Web Components. If you’re using Vite, it can be done like this:
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [
vue({
template: {
compilerOptions: {
// treat all tags with a dash as custom elements
isCustomElement: (tag) => tag.includes('-')
}
}
})
]
}
For more information, and how to configure for other setups (e.g. Vue CLI), please see the Vue docs.
Usage
Import the @nordhealth/components
and @nordhealth/css
packages in your main entry point:
// main.js
import '@nordhealth/css'
import '@nordhealth/components'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Then you can use the Nord’s components throughout your app. For instance, here is an example using a single-file component:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<nord-button variant="primary" @click="count++">
Count is: {{ count }}
</nord-button>
</template>
For optimal tree shaking, don’t import the @nordhealth/components
package in the main entry point. Instead, import the specific Nord components you're using in each component. For example:
<script setup>
import { ref } from 'vue'
import '@nordhealth/components/lib/Button'
const count = ref(0)
</script>
<template>
<nord-button variant="primary" @click="count++">
Count is: {{ count }}
</nord-button>
</template>
Our components also support the v-model
directive for two-way data binding:
<script setup>
import { ref } from 'vue'
const name = ref("")
</script>
<template>
<nord-input v-model="name" label="Name"></nord-input>
<output>{{ name }}</output>
</template>
However, for our Checkbox, Toggle, and Tag components you need to add type="checkbox"
to get v-model
working. Otherwise, Vue has no way of knowing that it should bind to the checked
property instead of the value
property. For example:
<script setup>
import { ref } from 'vue'
const selected = ref(false)
</script>
<template>
<nord-checkbox v-model="selected" type="checkbox" label="Label"></nord-checkbox>
<output>{{ selected }}</output>
</template>
Alternatively, you can always use the more verbose equivalent to v-model
:
<script setup>
import { ref } from 'vue'
const selected = ref(false)
</script>
<template>
<nord-checkbox :checked="selected" label="Label" @change="selected = $event.target.checked"></nord-checkbox>
<output>{{ selected }}</output>
</template>
Sometimes, you may need to pass complex data such as objects, arrays, and functions. These must be passed to a Web Component as properties rather than attributes. For the most part, Vue does the correct thing, and will handle this without any particular treatment.
However, there may be rare cases where you need to be more explicit that you’re supplying a property. For this, Vue has the syntax .propName="value"
, where propName
is the name of the property.
For instance, here is an example of passing a function to a component:
<script setup>
const isWeekend = (date) => date.getDay() === 0 || date.getDay() === 6
</script>
<template>
<nord-date-picker label="Date of birth" .isDateDisabled="isWeekend"></nord-date-picker>
</template>
You can integrate Nord components with RouterLink components from Vue Router like this:
<router-link to="/home" custom v-slot="{ navigate, href, route }">
<nord-button :href="href" @click="navigate">{{ route.fullPath }}</nord-button>
</router-link>
Check out the Vue Router documentation for more information.
Styling
Because our Web Components come with their styles built in, and our CSS Framework comes with utility helpers, you should very rarely need to add CSS to Vue components. However, if you need to add custom styles, please use our tokens instead of fixed values and add scoped CSS within Single-File Components:
<template>
<nord-badge>Status</nord-badge>
</template>
<style scoped>
nord-badge {
text-transform: capitalize;
}
</style>
Using the scoped
attribute will ensure the styles don’t leak out of your component and affect other components.
Check out the official Vue documentation for more information.
Types and editor integration
Nord's components work out of the box with Vue, but they don't provide any TypeScript support or editor integration. To improve the developer experience, we offer the @nordhealth/vue
package. This package only contains types and no code.
To integrate these into your project, install @nordhealth/components
as you normally would, along with @nordhealth/vue
. Then add the following to your tsconfig.json
in a TypeScript project, or jsconfig.json
in a JavaScript project:
{
"compilerOptions": {
"types": ["@nordhealth/vue"]
}
}
Now you will get full type checking and auto-completion in your Vue templates.
@nordhealth/components
package.
Can I use this in my own project?
Nord Design System is solely meant for building digital products and experiences for Nordhealth. Please see the terms of use for full license details.
Support
Need help with our Web Components? Please head over to the Support page for more guidelines and ways to contact us.
Attribution
Special thanks to the following projects that help make Nord possible.
- Components are built with Lit.
- Component metadata is generated by the Custom Elements Manifest Analyzer.
- Documentation is powered by Eleventy.