Skip to content
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 React and Vue making sure that the capabilities and features of each framework are fully functional. Angular is done in @zurich/angular-components
.
The library provide those ready-to-use wrapper components under the corresponding sub-reference (like @zurich/web-components/vue
or @zurich/web-components/react
).
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.
Safari <16.4 support
Do NOT use this approach if you want to give support to versions of Safari before 16.4. The absence of support for adoptedStyleSheets
before this patch makes the WebComponents approach extremely difficult to setup, requiring CSP special configs. Use @zurich/css-components
instead.
Install
...
We can use two different approaches for the <SRC>
value:
- Remote/CDN:
- Local/NPM:
@zurich/web-components
@zurich/web-components/styles.css
Via CDN
The fastest way to get going with @zurich/web-components
is to load it via a CDN. Just import the https://zds.zurich.com/0.5.18/@zurich/web-components/styles.css
file to the <head>
of your HTML and start using the CSS components.
We can import all the component using https://zds.zurich.com/0.5.18/@zurich/web-components
, but it is recommended to import then individually for performance.
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.5.18/@zurich/web-components/styles.css" />
<script type="module" src="https://zds.zurich.com/0.5.18/@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.5.18/@zurich/web-components/styles.css" />
<script type="module" src="https://zds.zurich.com/0.5.18/@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="../@zurich/web-components/styles.css" />
<script type="module" src="../@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:
📄
./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.📄
./a11y.css
: visual helpers for accessibility.
The types:
📄
./react-types
: type utils for React.📄
./vue-types
: type utils for Vue.
We can import single components by name using their standalone JS file.
Examples:
- Logo (local):
@zurich/web-components/dist/logo.js
ts
import "@zurich/web-components/dist/logo.js"
html
<head>
<script type="module" src="`https://zds.zurich.com/https://zds.zurich.com/0.5.18/@zurich/web-components/logo.js`"></script>
<head>
Complex attributes
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:
There are other options of use like the slots or the slotted configurations in some occasions
Or we can directly use JavaScript to pass them as properties of the instance:
html
<body>
<z-avatar-list id="my-avatar-list"></z-avatar-list>
<script type="module">
const avatarList = document.getElementById('my-avatar-list');
if (avatarList) avatarList.profiles = [
{ initials: 'AZ', color: 2 },
{ initials: 'AZ', color: 4 },
{ initials: 'AZ', color: 3, 'image-src': `/sample.webp` }
]
</script>
</body>
Check this documentation to know more about this options.
Accessibility
The WebComponents are handling the component level accessibility out of the box, but this might require in most cases to provide some attributes when the components is instantiated. Check the documentation of each component to check the corresponding requirements of each component.
For extra tooling, check this documentation.
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 for the wrappers are React and Vue.
INFO
For the Angular framework, check the @zurich/angular-components
package.
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 ZrIcon
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:line" />
</>
);
}
Using WebComponents directly
There's also a possibility for directly using the WebComponents. This requires some extra config for TypeScript:
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 '@zurich/web-components/icon';
export default function App () {
return (
<>
<z-icon icon="close:line" />
</>
);
}
json
{
"compilerOptions": {
"types": [
"./react.d.ts",
]
},
}
ts
import type { ZDSReactComponents } from '@zurich/web-components/react-types';
declare global {
namespace JSX {
interface IntrinsicElements extends ZDSReactComponents {
}
}
}
Vue
This is the basic setup for Vue with ZvIcon
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:line" />
</template>
Custom elements compilation
In case of using Vite and you're receiving a message saying that z-*
components are not recognized, 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
.
If you use the directly the Vue wrappers (@zurich/web-components/vue
), this warning should not appearing case of not setting the compilerOptions.isCustomElement
, but might appear if you use the WebComponents directly.
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 the 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
:model="str"
@change="(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: {
/** ... */
model: {
type: String as PropType<ZTextInputProps['model']>,
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['model']) => { },
},
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>
};
Next.js
Since Next.js combines the Node runtime with browser components, it requires an specific compilation mode to make it 100% compatible with the approach: commonJS
.
The component in this case is split into two parts:
React wrapper: the one build as CJS and that is interacted with by the Next.js. It's the faceplate that will be used in the code, being a contract for when it's running in the client. It's part of the bundle or optimized by Next.js as plain HTML to be delivered.
WebComponent: the actual code and styles of the component, with is directly loaded in the client via CDN and starts to be fetched at the very beginning of the page load for better performance independently of the content. This is done via Next's
<Script>
component and the URL of the specific component in the CDN.
This approach still requires to import styles.scss
(once per page).
tsx
import Script from 'next/script';
import '@zurich/web-components/styles.css';
import { WC_CDN_ROOT } from '@zurich/web-components/info';
import { ZrIcon } from '@zurich/web-components/cjs/icon';
export default function Home () {
return (
<>
<Script type="module" src={`${WC_CDN_ROOT}/text-input.js`} strategy='beforeInteractive' />
<ZrIcon icon="close" />
</>
);
}
You can wrap everything in your own component too:
tsx
import Script from 'next/script';
import { WC_CDN_ROOT } from '@zurich/web-components/info';
import { ZrTextInput, type ZrTextInput_Props } from '@zurich/web-components/cjs/text-input';
/** ## ZDS Text input
*
* A lovely text input component 💚 */
export const ZDSTextInput = <T extends string = string> (props: ZrTextInput_Props<T>) => {
return (
<>
<Script type="module" src={`${WC_CDN_ROOT}/text-input.js`} strategy='beforeInteractive' />
<ZrTextInput {...props} />
</>
);
};
export default ZDSTextInput;
tsx
import { useState } from 'react';
import '@zurich/web-components/styles.css';
import { ZDSTextInput } from '@/components/TextInput';
export default function Home () {
const [value, setValue] = useState('');
return (
<ZDSTextInput label="My label" model={value} onChange={setValue}/>
);
}