Web components
The @zurich/web-components
NPM package is a package that uses JavaScript and CSS. The components are developed using Lit and then wrapped to be easily used with the main Frameworks Angular, React, and Vue making sure that the capabilities and features of each framework are fully functional. The library provide those ready-to-use components under the corresponding sub-reference (like @zurich/web-components/vue
).
Everything is adapted for the use of TypeScript (even for the frameworks) and the common development setups.
Disclaimer
Read first the @zurich/design-tokens
installation guide since this package depends on that one.
Install
...
We can use two different approaches for the <SRC>
value:
- Remote/CDN:
- Local/NPM:
https://zds.zurich.com/0.3.7/@zurich/web-components
https://zds.zurich.com/0.3.7/@zurich/web-components/styles.css
Via CDN
The fastest way to get going with @zurich/css-components
is to load it via a CDN. Just import the https://zds.zurich.com/0.3.7/@zurich/web-components/styles.css
file to the <head>
of your HTML and start using the CSS components. Remember that the CSS components require the @zurich/design-tokens
in order to be shown properly, so we need to import them first.
in order to import anything via CDN, you need use the file inside the CDN/
folder to avoiding the call for local dependencies and breaking the modules' chain.
Here's the code example on how to import the inside the <head>
:
html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://zds.zurich.com/0.3.7/@zurich/web-components/styles.css" />
<script type="module" src="https://zds.zurich.com/0.3.7/@zurich/web-components/index.js"></script>
</head>
</html>
Here's an example on how we use the components in a vanilla HTML file.
You can also import individual components using the name of the component within inside the subfolder CDN/
. Here's an example with the icon component, but remember that this one requires also the used Icon:
html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://zds.zurich.com/0.3.7/@zurich/web-components/styles.css" />
<script type="module" src="https://zds.zurich.com/0.3.7/@zurich/web-components/icon.js"></script>
</head>
</html>
Here's an example on how we use the icon component in a vanilla HTML file.
html
<body z-theme="dark">
<link rel="stylesheet" href="/0.3.7/@zurich/web-components/styles.css" />
<script type="module" src="/0.3.7/@zurich/web-components/index.js"></script>
<z-logo inline></z-logo>
</body>
Local installation
For NPM-style build systems or/and local development, you can install @zurich/web-components
via NPM. Just use the installation command of you favorite package manager (NPM, Yarn, PNPM, Bun, etc.). The @zurich/design-tokens
packages is set as a dependency of @zurich/web-components
, so we don't require to explicitly install the package.
bash
npm i @zurich/web-components
Then access the CSS files inside the distribution folder and import the CSS in your JS/TS. Remember that the @zurich/design-tokens
import needs to be done also first for the components to utilize the tokens:
ts
import '@zurich/web-components/styles.css';
import '@zurich/web-components';
Individual components can be also imported for better WPO, keeping the @zurich/web-components/styles.css
import:
ts
import '@zurich/web-components/styles.css';
import '@zurich/web-components/safe-space.js';
import '@zurich/web-components/profile.js';
import '@zurich/web-components/button.js';
Imports
Tha main imports of the packages are:
./index
: the main file that contains all the components.
The export per framework are under the corresponding framework name:
./angular
: the main file that contains all the components prepared for tree-shaking and the utilities for Angular../react
: the main file that contains all the components prepared for tree-shaking and the utilities for React. The components are wrapped in a React component function to allow React to use the Web component the right way../vue
: the main file that contains all the components prepared for tree-shaking and the utilities for Vue.
The styles:
./styles.css
: a CSS stylesheet file the contains the all the necessary CSS styles for components to be seen properly and with optimization for shared styles. This export already includes the./index.css
export of the@zurich/design-tokens
package.
We can import single components by name using their standalone JS file. Not suitable for React components.
Examples:
- Logo (local):
@zurich/web-components/dist/logo.js
- Logo (CDN):
https://zds.zurich.com/0.3.7/@zurich/web-components/logo.js
Import tree
Here's an schema of the imports tree:
Complex properties
Attention!
If you're using the web component in plain HTML and you want to pass an Object
or Array
, make sure to parse it as JSON or using JS to inject the property. When you parse it as JSON, you won't be able to properly scape the keys and values quotations with "
, so our recommendation is to use single quotes ('
) for the whole attribute and then set the whole object inside in JSOn format.
An example:
html
<z-avatar-list
profiles='[{"initials":"AZ","color":2},{"initials":"AZ","color":4},{"initial":"AZ","color":3,"image-src":"../sample.png"}]'
>
</z-avatar-list>
Frameworks
In order to ease the use of the #zurich/web-components
library, we have created a wrapper per component and per supported framework so they can be used as components natively developed for that framework, using directives, TypeScript, events, callbacks, slots, and all the frameworks's syntax.
The supported frameworks are Angular, React and Vue.
Angular
For the WebComponent integration with Angular we use Angular 17 due to major improvements done that make things easier.
Check first that you have installed also the design tokens and add the "node_modules/@zurich/web-components/dist/styles.css"
import in the styles
.
Then, the component of module must import the corresponding component, in this case we use ZaIcon
as an example:
json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"newProjectRoot": "projects",
"projects": {
"my-app": {
"architect": {
"build": {
"styles": [
"node_modules/@zurich/web-components/dist/styles.css",
],
},
},
}
}
}
ts
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
ts
import '@angular/compiler';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { ZaIcon } from '@zurich/web-components/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, ZaIcon],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
}
html
<za-icon icon="close:outline"></za-icon>
React
The WebComponent and React require some major changes to make a proper integration. Lit for React is one of the tools that allow so, but we've decided to make our own fine tunning. There's only the possibility of using the ZDS components inside the components files and not as a global component to avoid problems.
So we only need to import the necessary components from '@zurich/web-components/react'
in the corresponding context.
This is the basic setup for React with ZIcon
as an example:
tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import '@zurich/web-components/styles.css';
const root = document.getElementById('root') as HTMLElement;
ReactDOM
.createRoot(root)
.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
tsx
import { ZrIcon } from '@zurich/web-components/react';
export default function App () {
return (
<>
<ZrIcon icon="close:outline" />
</>
);
}
Vue
Also, in case of using Vie, make sure to add the necessary configuration in the vite.config.ts
file for Vue 3 to recognize the custom elements instead of trying to render them as part of the @vitejs/plugin-vue
Vite's plugin configuration:
This is the basic setup for React with ZIcon
as an example:
ts
import { createApp } from 'vue';
import App from './App.vue';
import '@zurich/web-components/styles.css';
createApp(App).mount('#app');
html
<script lang="ts" setup>
import { ZvIcon } from '@zurich/web-components/vue';
<script>
<template>
<zv-icon icon="close:outline" />
</template>
ts
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('z-'),
},
}
}),
],
});
Global installation
It can be done importing @zurich/web-components
in the main.ts
file.
ts
import { createApp } from 'vue';
import App from './App.vue';
import '@zurich/web-components/styles.css';
import { ZvIcon } from '@zurich/web-components/vue';
createApp(App)
// A different Name can be set, but don't recommend so to keep consistency
.component('ZvIcon', ZvIcon)
.mount('#app');
For TypeScript support:
ts
// vite-env.d.ts
import type { ZDSVueComponents } from '@zurich/web-components/vue';
declare module '@vue/runtime-core' {
interface GlobalComponents extends ZDSVueComponents { }
}
Vue 2 compatibility
Attention!
This package is compatible with Vue 2 as it uses WebComponents, but you should not use the configuration for Vue since that's done for Vue 3.
Since Vue 2 reached the end of its life on on December 31st of 2023, the ZDS decided not to provide specific support for Vue 2 even when it's used in projects inside Zurich. This decision is based on several reasons like:
- Having more time to better support other frameworks.
- Embracing the migration to Vue 3 and not giving extra excuses for not doing that migration in old projects.
- Solving lots of development issues caused by having to support two versions of teh same dependency.
- Ensuring a better developer experience when the development is under our control.
The use of the v-model
directive won't work, since Vue 2 uses value
and @input
attributes and events and even when the value
attribute will work, the @input
event return a CustomEvent
with the payload in the detail
property and not the actual payload, so the assignation in not properly done. This is how it needs to be done:
vue
<script lang="ts">
import '@zurich/web-components/text-input.js';
export default {
data () {
return {
str: 'My value',
};
},
};
</script>
<template>
<z-text-input
:value="str"
@input="(e) => { str = e.detail }"
>
</z-text-input>
</template>
Another option is to create your own wrappers. For that you can use this approach:
Attention!
Do not use the same name for the tag to avoid recursive calls! We recommend to change the prefix from z-
to vue-
.
Also, make sure to set the compilerOptions.isCustomElement
to ignore tags stating with z-
as explained in the Vue 3 section
vue
<script lang="ts">
import { VueTextInput } from './VueTextInput';
export default {
components: [ VueTextInput ],
data () {
return {
str: 'My value',
};
},
};
</script>
<template>
<zv-text-input v-model="str" />
</template>
ts
import { defineComponent, type PropType } from 'vue';
import '@zurich/web-components/text-input.js';
import type { ZTextInputEvents, ZTextInputProps } from '@zurich/dev-utils/code';
import type { Vue2Props } from './_shared';
/** ## `<VueTextInput />` */
export const VueTextInput = defineComponent({
props: {
/** ... */
value: {
type: String as PropType<ZTextInputProps['value']>,
required: true,
},
/** ... */
label: {
type: String as PropType<ZTextInputProps['label']>,
required: true,
},
/** ... */
config: {
type: String as PropType<ZTextInputProps['config']>
},
/** ... */
name: {
type: String as PropType<ZTextInputProps['name']>
},
/** ... */
disabled: {
type: Boolean as PropType<ZTextInputProps['disabled']>,
},
/** ... */
required: {
type: Boolean as PropType<ZTextInputProps['required']>
},
/** ... */
'max-length': {
type: Number as PropType<ZTextInputProps['max-length']>
},
/** ... */
'data-list': {
type: Array as PropType<ZTextInputProps['dataList']>
},
} satisfies Vue2Props<ZTextInputProps>,
emits: {
input: (e: ZTextInputProps['value']) => { },
},
render: function (h) {
return h('z-text-input', {
attrs: {
...this.$props,
},
on: {
change: (e) => {
e.stopPropagation();
},
input: (e) => {
e.stopPropagation();
this.$emit('input', e.detail);
},
} satisfies Required<ZTextInputEvents>
});
}
});
ts
import type { PropType } from 'vue';
type Vue2Prop<T, K extends keyof T> =
{ type: PropType<T[K]>; }
& (undefined extends T[K] ? {} : { required: true; });
export type Vue2Props<T> =
{
[K in keyof Required<T> as K extends 'z-color' ? never : K]: Vue2Prop<T, K>
}
& {
[K in keyof Required<T> as K extends 'z-color' ? 'zColor' : never]: Vue2Prop<T, K>
};