Skip to content

Rent a car: A custom item renderer

The default dropdown item template is just a label. Real applications usually want richer items — a thumbnail, the title, secondary metadata. GolemUI lets you supply a custom item renderer for that.

The car items now carry an emoji thumbnail and a price:

items: [
{ id: 'compact', label: 'Compact', img: '🚗', price: 35 },
{ id: 'suv', label: 'SUV', img: '🚙', price: 75 },
{ id: 'convertible', label: 'Convertible', img: '🏎️', price: 110 },
{ id: 'luxury', label: 'Luxury', img: '🚘', price: 180 },
],

Item renderers are framework-specific components.

CarItemRenderer.tsx
import * as React from 'react';
import type { ReactItemRenderer } from '@golemui/react';
type Car = { id: string; label: string; img: string; price: number };
export const CarItemRenderer: ReactItemRenderer<Car> = ({ item }) => (
<div className="car-item">
<span className="car-item__img">{item.img}</span>
<span className="car-item__label">{item.label}</span>
<span className="car-item__price">${item.price}/day</span>
</div>
);

A few lines of CSS to lay the renderer out:

.car-item {
display: flex;
align-items: center;
gap: var(--gui-space-3);
}
.car-item__img { font-size: 1.4em; }
.car-item__label { flex: 1; }
.car-item__price { color: var(--gui-color-fg--secondary); }

Add it to formConfig.itemRenderers:

const formConfig = {
itemRenderers: {
carItemRenderer: CarItemRenderer,
},
};

The dropdown’s itemRenderer prop carries the name you registered:

03-renderer.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.dropdown('car', {
labelField: 'label',
valueField: 'id',
itemRenderer: 'carItemRenderer',
items: [
{
id: 'compact',
label: 'Compact',
img: '🚗',
price: 35,
},
{
id: 'suv',
label: 'SUV',
img: '🚙',
price: 75,
},
{
id: 'convertible',
label: 'Convertible',
img: '🏎️',
price: 110,
},
{
id: 'luxury',
label: 'Luxury',
img: '🚘',
price: 180,
},
],
label: 'Select car',
validator: {
type: 'string',
required: true,
},
}),
gui.inputs.dropdown('collectOffice', {
labelField: 'label',
valueField: 'id',
items: [
{
id: 'lhr',
label: 'London Heathrow',
},
{
id: 'cdg',
label: 'Paris CDG',
},
{
id: 'fra',
label: 'Frankfurt Main',
},
],
label: 'Collect from office',
validator: {
type: 'string',
required: true,
},
}),
gui.inputs.booleanInput('differentReturn', {
label: 'Choose a different return location',
}),
gui.inputs.dropdown('returnOffice', {
labelField: 'label',
valueField: 'id',
items: [
{
id: 'lhr',
label: 'London Heathrow',
},
{
id: 'cdg',
label: 'Paris CDG',
},
{
id: 'fra',
label: 'Frankfurt Main',
},
],
label: 'Return to office',
validator: {
type: 'string',
required: true,
},
}),
gui.inputs.rangeCalendar('rentalDates', {
label: 'Rental dates',
validator: {
type: 'array',
required: true,
minItems: 2,
maxItems: 2,
},
}),
gui.inputs.radiogroup('rentalType', {
options: [
{
label: 'Daily',
value: 'daily',
},
{
label: 'Weekly',
value: 'weekly',
},
{
label: 'Monthly',
value: 'monthly',
},
],
label: 'Rental type',
validator: {
type: 'string',
required: true,
},
}),
gui.inputs.booleanInput('driverOver25', {
label: 'Driver aged over 25',
validator: {
type: 'boolean',
const: true,
required: true,
},
}),
gui.inputs.booleanInput('hasDiscountCode', {
label: 'I have a discount code',
}),
gui.inputs.textInput('discountCode', {
label: 'Discount code',
validator: {
type: 'string',
required: true,
minLength: 4,
},
}),
gui.actions.button({
label: 'Reserve',
uid: 'submit',
onClick: 'submit',
}),
];
03-renderer.json
{
"states": {
"differentReturn": "$form.differentReturn === true",
"hasDiscount": "$form.hasDiscountCode === true"
},
"form": [
{
"kind": "input",
"type": "dropdown",
"path": "car",
"label": "Select car",
"validator": { "type": "string", "required": true },
"props": {
"labelField": "label",
"valueField": "id",
"itemRenderer": "carItemRenderer",
"items": [
{ "id": "compact", "label": "Compact", "img": "🚗", "price": 35 },
{ "id": "suv", "label": "SUV", "img": "🚙", "price": 75 },
{ "id": "convertible", "label": "Convertible", "img": "🏎️", "price": 110 },
{ "id": "luxury", "label": "Luxury", "img": "🚘", "price": 180 }
]
}
},
{
"kind": "input",
"type": "dropdown",
"path": "collectOffice",
"label": "Collect from office",
"validator": { "type": "string", "required": true },
"props": {
"labelField": "label",
"valueField": "id",
"items": [
{ "id": "lhr", "label": "London Heathrow" },
{ "id": "cdg", "label": "Paris CDG" },
{ "id": "fra", "label": "Frankfurt Main" }
]
}
},
{
"kind": "input",
"type": "toggle",
"path": "differentReturn",
"label": "Choose a different return location"
},
{
"kind": "input",
"type": "dropdown",
"path": "returnOffice",
"label": "Return to office",
"include": { "in": ["differentReturn"] },
"validator": { "type": "string", "required": true },
"props": {
"labelField": "label",
"valueField": "id",
"items": [
{ "id": "lhr", "label": "London Heathrow" },
{ "id": "cdg", "label": "Paris CDG" },
{ "id": "fra", "label": "Frankfurt Main" }
]
}
},
{
"kind": "input",
"type": "rangeCalendar",
"path": "rentalDates",
"label": "Rental dates",
"validator": { "type": "array", "required": true, "minItems": 2, "maxItems": 2 }
},
{
"kind": "input",
"type": "radiogroup",
"path": "rentalType",
"label": "Rental type",
"validator": { "type": "string", "required": true },
"props": {
"options": [
{ "label": "Daily", "value": "daily" },
{ "label": "Weekly", "value": "weekly" },
{ "label": "Monthly", "value": "monthly" }
]
}
},
{
"kind": "input",
"type": "toggle",
"path": "driverOver25",
"label": "Driver aged over 25",
"validator": { "type": "boolean", "const": true, "required": true }
},
{
"kind": "input",
"type": "toggle",
"path": "hasDiscountCode",
"label": "I have a discount code"
},
{
"kind": "input",
"type": "textinput",
"path": "discountCode",
"label": "Discount code",
"include": { "in": ["hasDiscount"] },
"validator": { "type": "string", "required": true, "minLength": 4 }
},
{
"kind": "action",
"type": "button",
"label": "Reserve",
"uid": "submit",
"on": { "click": "submit" }
}
]
}

Search as you type →