Skip to content
Components, switch product

Installation

Terminal window
npm install @golemui/gui-components lit

lit is a peer dependency, version 3.3.1 or later.

Import the stylesheet once for your whole app, for example in your entry file:

import '@golemui/gui-components/index.css';

It holds the design tokens and the styles of every element. To load the tokens on their own, or to add the optional Clay theme, see Stylesheets.

Each element has its own entry point, which registers that element and nothing else:

import '@golemui/gui-components/textinput';
import '@golemui/gui-components/date-picker';

Then use the tags anywhere HTML goes:

<gui-textinput label="Email" name="email" required></gui-textinput>

To register every element at once, import the package itself:

import '@golemui/gui-components';

Each component page shows its entry point and the code for every framework.

@golemui/gui-components/react has a component for every element, with typed props and an on* prop for each event. It needs react 18 or later.

import { GuiTextinput } from '@golemui/gui-components/react';
<GuiTextinput
label="Email"
onGuiChange={(event) => save(event.detail.value)}
/>;

Add CUSTOM_ELEMENTS_SCHEMA to the component that uses the tags, then bind properties and events as usual:

@Component({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<gui-textinput label="Email" [value]="email" (gui-change)="onChange($event)"></gui-textinput>
`,
})

Tell Vue the gui-* tags are custom elements, for example in vite.config.ts:

vue({
template: {
compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') },
},
});

For typed props and @gui-* handlers in templates, add the typings once, for example in env.d.ts:

import type {} from '@golemui/gui-components/vue';

Use the tags in your templates, and bind arrays and objects as properties:

html`<gui-select
label="Plan"
.options=${plans}
@gui-change=${onChange}
></gui-select>`;

GolemUI Components load in Node without a DOM, so a framework that renders on the server can import them. Out of the box the server sends the gui-* tags empty, and the browser fills them in. To send their content in the server HTML too, install @lit-labs/ssr 4.1 or later and set up your framework as below:

Terminal window
npm install @lit-labs/ssr

@golemui/gui-components/ssr is the server-only entry point these setups use. How it works and its limits are in Server rendering.

Import the entry point once in your server entry. The React components then render their content on the server:

entry-server.tsx
import { renderToString } from 'react-dom/server';
import '@golemui/gui-components/ssr';

Each component renders its own element, so streaming renders work too, such as renderToPipeableStream. The components keep the server content until React has hydrated them.

Use the React components. They are client components ("use client"), so they work in the App Router. To render their content on the server, import the entry point in the root layout, a Server Component:

app/layout.tsx
import '@golemui/gui-components/ssr';
import '@golemui/gui-components/index.css';

Import it there rather than in instrumentation.ts, which doesn’t run when the build prerenders static pages. The components then render their content in the App Router’s streaming render too.

Set up Angular, and import the elements in src/main.server.ts as well as in src/main.ts, so they render on the server. Then render them just before Angular serializes the page, with a provider in your server config:

app.config.server.ts
import { DOCUMENT, inject } from '@angular/core';
import { BEFORE_APP_SERIALIZED } from '@angular/platform-server';
import { renderElementsInDocument } from '@golemui/gui-components/ssr';
export const provideGolemuiServerRendering = () => ({
provide: BEFORE_APP_SERIALIZED,
multi: true,
useFactory: () => {
const document = inject(DOCUMENT);
return () => renderElementsInDocument(document);
},
});

Add provideGolemuiServerRendering() to the providers next to provideServerRendering(). renderElementsInDocument reads the values bound as properties, such as [value] and [options], as well as the attributes, so every binding reaches the server HTML.

Analog renders its pages with Angular on the server. These steps start from a new Analog project.

Load the stylesheet in src/styles.css, which the starter links from index.html:

src/styles.css
@import '@golemui/gui-components/index.css';

Import the elements you use in one file:

src/elements.ts
import '@golemui/gui-components/textinput';
import '@golemui/gui-components/select';

Then import that file on both sides: in the browser, so the elements upgrade, and on the server, so they render.

src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';
import './elements';
bootstrapApplication(App, appConfig);
src/main.server.ts
import '@angular/platform-server/init';
import { render } from '@analogjs/router/server';
import { App } from './app/app';
import { config } from './app/app.config.server';
import './elements';
export default render(App, config);

Add a provider to the server config that renders the elements just before Angular serializes the page:

src/app/app.config.server.ts
import {
type ApplicationConfig,
DOCUMENT,
inject,
mergeApplicationConfig,
} from '@angular/core';
import {
BEFORE_APP_SERIALIZED,
provideServerRendering,
} from '@angular/platform-server';
import { renderElementsInDocument } from '@golemui/gui-components/ssr';
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
{
provide: BEFORE_APP_SERIALIZED,
multi: true,
useFactory: () => {
const document = inject(DOCUMENT);
return () => renderElementsInDocument(document);
},
},
],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);

Use the tags in your pages, with CUSTOM_ELEMENTS_SCHEMA:

src/app/pages/index.page.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
@Component({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<gui-textinput name="email" label="Email" [value]="email()"></gui-textinput>
<gui-select
name="plan"
label="Plan"
[options]="plans"
value="pro"
></gui-select>
`,
})
export default class HomePage {
email = signal('ada@example.com');
plans = [
{ label: 'Free', value: 'free' },
{ label: 'Pro', value: 'pro' },
];
}

renderElementsInDocument reads the values bound as properties, such as [value] and [options], so the server HTML has the email and both options.

Set up Vue, then pass the HTML Vue rendered through renderElementsInHtml:

entry-server.ts
import { renderToString } from 'vue/server-renderer';
import { renderElementsInHtml } from '@golemui/gui-components/ssr';
import '@golemui/gui-components';
const html = renderElementsInHtml(await renderToString(app));

renderElementsInHtml renders the empty elements in the HTML and leaves the rest of it byte for byte as it was. It renders each element from its attributes, so a value bound as a property is missing from the server HTML. See Limits.

Add the module to nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
modules: ['@golemui/gui-components/nuxt'],
css: ['@golemui/gui-components/index.css'],
});

It tells Vue the gui-* tags are custom elements and renders the elements of every page Nuxt sends. Its options go under golemui:

OptionDefaultWhat it does
serverRenderingtrueRenders the elements’ content on the server. With false, Nuxt sends their tags empty.
prefixes['gui-']The tag prefixes Vue compiles as custom elements. Add the prefix of a widget set of your own.

With serverRendering: false, @lit-labs/ssr isn’t needed. Like Vue, the module renders each element from its attributes. See Limits.

Render a Lit template with renderTemplate:

import { html } from 'lit';
import { renderTemplate } from '@golemui/gui-components/ssr';
import '@golemui/gui-components/textinput';
const markup = await renderTemplate(
html`<gui-textinput uid="email" name="email" label="Email"></gui-textinput>`,
);

The markup holds every element inert, with a defer-hydration attribute. In the browser, import the elements and call resumeServerRendered():

import { resumeServerRendered } from '@golemui/gui-components';
import '@golemui/gui-components/textinput';
resumeServerRendered();

Pass the values as attributes. The browser builds the elements from the HTML, so a property binding such as .options=${options} reaches the server render only.

Write the tags in your pages and register the elements with a client script:

src/pages/index.astro
---
import '@golemui/gui-components/index.css';
---
<gui-textinput label="Email" name="email"></gui-textinput>
<script>
import '@golemui/gui-components/textinput';
</script>

Astro sends the tags as you wrote them. To render their content, add a middleware that passes each HTML response through renderElementsInHtml:

src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { renderElementsInHtml } from '@golemui/gui-components/ssr';
import '@golemui/gui-components';
export const onRequest = defineMiddleware(async (_context, next) => {
const response = await next();
if (!response.headers.get('content-type')?.startsWith('text/html'))
return response;
return new Response(renderElementsInHtml(await response.text()), response);
});

The middleware runs for the pages Astro renders on demand and for the pages it prerenders at build time. Pass every value as an attribute, and arrays and objects as JSON, such as options={JSON.stringify(plans)}.

Each element exports its class and its events map (GuiTextinputEvents). GuiEventMap gives the type of each event:

import type { GuiEventMap, GuiTextinputEvents } from '@golemui/gui-components';
type ChangeEvent = GuiEventMap<typeof GuiTextinputEvents>['gui-change'];