Rent a car: Introduction
This tutorial walks through building a complete Rent a Car form. By the end, you’ll have used most of the features GolemUI offers — layout, validators, states, custom item renderers, async data with debouncing, and form submission — in one connected example.
What you’ll build
Section titled “What you’ll build”A form that lets a customer:
- pick a car model from a dropdown (search-as-you-type, custom item renderer with image and details),
- pick a collect-from office dropdown,
- toggle “Choose a different return location” to reveal a return-to office dropdown,
- pick a rental dates range (a range calendar),
- pick a rental type from a radio group,
- confirm the driver is aged over 25 with a toggle,
- toggle “I have a discount code” to reveal a discount code input,
- submit the form, with the submit button disabled until everything is valid.
Tutorial chapters
Section titled “Tutorial chapters”Work through them in order — each chapter builds on the previous one.
- Parts of the form — author the static layout with all the input widgets.
- States and conditional rendering — wire the toggles to show/hide fields.
- Validation errors — wire validators to every required field, then replace the default Zod messages with friendlier copy.
- A custom item renderer — design the per-car list item with image, name, and price.
- Search as you type — load the car list from a service, filter server-side, debounce the input.
- Submitting the form — capture the submit event and read the data.
Layout convention
Section titled “Layout convention”This tutorial follows the GolemUI default:
gui.layouts.grid for rows (CSS subgrid keeps labels and inputs aligned), gui.layouts.flex for columns.
{ "kind": "layout", "type": "grid" } for rows (CSS subgrid keeps labels and inputs aligned), { "kind": "layout", "type": "flex" } for columns.
Where the form definition lives
Section titled “Where the form definition lives”In Installation we authored the form definition inline, in the same file as the React / Angular / Lit / Vue component (or in main.js for vanilla JS). That keeps everything in one place while you’re getting started, but for any non-trivial form it’s a maintenance trap — the component file balloons, and reusing the form shape from a test, a script, or a backend becomes awkward.
From this chapter on, the tutorial moves the form definition into its own dedicated file:
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.dropdown('car', { /* ... */ }), // …more widgets…];{ "form": [ { "kind": "input", "type": "dropdown", "path": "car" } ]}Then import that form from your component and pass it as formDef in the config object. The wiring code is identical to what you saw in Installation — only the source of formDef changes.
import { GuiForm } from '@golemui/gui-react';import rentACarForm from './rent-a-car.form';
export function RentACarPage() { return <GuiForm config={{ formDef: rentACarForm }} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { FormComponent } from '@golemui/gui-angular';import rentACarForm from './rent-a-car.form';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-rent-a-car', template: `<gui-form [config]="config"></gui-form>`,})export class RentACarPage { protected config = { formDef: rentACarForm };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-lit';import rentACarForm from './rent-a-car.form';
@customElement('rent-a-car-page')export class RentACarPage extends LitElement { config = { formDef: rentACarForm };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { GuiForm } from '@golemui/gui-vue';import rentACarForm from './rent-a-car.form';
const config = { formDef: rentACarForm };</script>
<template> <GuiForm :config="config" /></template><gui-form id="app-form"></gui-form><script type="module" src="/src/main.js"></script>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import rentACarForm from './rent-a-car.form';
const form = document.getElementById('app-form');form.config = { formDef: rentACarForm };import { GuiForm } from '@golemui/gui-react';import rentACarForm from './rent-a-car.form.json';
export function RentACarPage() { return <GuiForm config={{ formDef: rentACarForm }} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { FormComponent } from '@golemui/gui-angular';import rentACarForm from './rent-a-car.form.json';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-rent-a-car', template: `<gui-form [config]="config"></gui-form>`,})export class RentACarPage { protected config = { formDef: rentACarForm };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-lit';import rentACarForm from './rent-a-car.form.json';
@customElement('rent-a-car-page')export class RentACarPage extends LitElement { config = { formDef: rentACarForm };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { GuiForm } from '@golemui/gui-vue';import rentACarForm from './rent-a-car.form.json';
const config = { formDef: rentACarForm };</script>
<template> <GuiForm :config="config" /></template><gui-form id="app-form"></gui-form><script type="module" src="/src/main.js"></script>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import rentACarForm from './rent-a-car.form.json';
const form = document.getElementById('app-form');form.config = { formDef: rentACarForm };JSON imports require "resolveJsonModule": true in your tsconfig.json — Vite, Angular CLI, and most starters have it on by default. (Vanilla JS users on plain Vite get JSON imports for free, no tsconfig needed.)
The wiring above stays the same as the form grows; only the form definition file changes.
See also
Section titled “See also”- Form Definition API — every shortcut you’ll see in this tutorial.
- Features — deep dives into each capability you’ll touch.