Layout components
A definition's layout puts its fields in sections, rows of a 12-column grid and steps, and leaves slots for your content. <JsonFields> renders it with a component of yours for each layout type. Below you will learn how to write them.
Sections and rows
A section gets the section as written, and renders its items. A row gets the span of each cell, and renders each cell:
<script setup lang="ts">
import type {
SectionProps,
SectionSlots,
} from '@whatsaaaa/form-builder-vue'
defineProps<SectionProps>()
defineSlots<SectionSlots>()
</script>
<template>
<fieldset>
<legend>{{ section.label }}</legend>
<p v-if="section.description">{{ section.description }}</p>
<slot />
</fieldset>
</template><script setup lang="ts">
import type { RowProps, RowSlots } from '@whatsaaaa/form-builder-vue'
defineProps<RowProps>()
defineSlots<RowSlots>()
</script>
<template>
<div class="row">
<div
v-for="(span, index) in spans"
:key="index"
:style="{ gridColumn: `span ${span}` }"
>
<slot :index="index" />
</div>
</div>
</template>
<style scoped>
.row {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1rem;
}
/* The spans are for a wide screen. On a narrow one, each cell takes the whole row. */
@media (max-width: 640px) {
.row > * {
grid-column: 1 / -1 !important;
}
}
</style>function LayoutSection({ section, children }: SectionProps) {
return (
<fieldset>
<legend>{section.label}</legend>
{section.description && <p>{section.description}</p>}
{children}
</fieldset>
)
}
// One line of a 12-column grid. The .row class is in the CSS below.
function LayoutRow({ spans, children }: RowProps) {
return (
<div className="row">
{spans.map((span, index) => (
<div key={index} style={{ gridColumn: `span ${span}` }}>
{children(index)}
</div>
))}
</div>
)
}
export const layoutComponents: LayoutComponents = {
section: LayoutSection,
row: LayoutRow,
}.row {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1rem;
}
/* The spans are for a wide screen. On a narrow one, each cell takes the whole row. */
@media (max-width: 640px) {
.row > * {
grid-column: 1 / -1 !important;
}
}| Layout type | Props | Content |
|---|---|---|
section | section, as written, with its id, label, description and ui | the section's items |
row | row, as written, with its id and ui, and spans, one per cell | the cell at each index |
In Vue, the content is the default slot, with { index } for a row. In React, it is children, and children(index) for a row.
Rendering the layout
Pass the components as layoutComponents, and fill the layout's slots: with named slots in Vue, or with slots in React:
<script setup lang="ts">
import { Form } from '@formisch/vue'
import type { JsonForm } from '@whatsaaaa/form-builder-schema'
import { JsonFields, useJsonForm } from '@whatsaaaa/form-builder-vue'
import { components } from './all-fields'
import LayoutRow from './LayoutRow.vue'
import LayoutSection from './LayoutSection.vue'
const { definition } = defineProps<{ definition: JsonForm }>()
const jsonForm = useJsonForm(definition, {
components,
layoutComponents: { section: LayoutSection, row: LayoutRow },
})
function save(output: unknown) {
console.log(output)
}
</script>
<template>
<Form :of="jsonForm.form" @submit="save">
<JsonFields :of="jsonForm">
<template #submit>
<button type="submit">Place order</button>
</template>
</JsonFields>
</Form>
</template>export function CheckoutForm({ definition }: { definition: JsonForm }) {
const jsonForm = useJsonForm(definition, { components, layoutComponents })
function save(output: unknown) {
console.log(output)
}
return (
<Form of={jsonForm.form} onSubmit={save}>
<JsonFields
of={jsonForm}
slots={{ submit: <button type="submit">Place order</button> }}
/>
</Form>
)
}- A layout type that the layout uses without a component, or a slot without content, throws a
TypeErrorthat names each one when<JsonFields>first renders, for every step, not only the current one. - A definition without sections or rows needs no layout components, and one without a
layoutrenders its fields in order. - Display elements written in the layout render with your field components, like those in
fields. - A section or a row whose fields are all hidden or left out with
exceptdrops out. The other cells of a row keep their spans.
Screen sizes
The spans are for a wide screen. The layout says nothing about narrow ones, so your row component decides. The rows above give each cell the whole row below 640 pixels. Read row.ui or section.ui for hints of your own, like { "variant": "card" }.
Next steps
Continue to multi-step forms to render a layout with steps.