Skip to content

useFormSurface ​

How a SchemaForm learns what it is rendering inside. The container declares itself; the form obeys.

Import ​

ts
import { DEFAULT_FORM_SURFACE, FORM_SURFACE, useFormSurface } from '@core/schema/formSurface'

Signature ​

ts
function useFormSurface(): FormSurface

interface FormSurface {
  flat: boolean
  footer: boolean
}

const FORM_SURFACE: InjectionKey<FormSurface>
const DEFAULT_FORM_SURFACE: FormSurface

Returns ​

KeyTypeDescription
flatbooleanOne flat grid: no group headings, fields reordered by type, uploads and long text on their own full-width row.
footerbooleanThe form draws its own footer and prepends a Cancel.

DEFAULT_FORM_SURFACE is { flat: false, footer: false } — what a SchemaForm with no declaring container gets.

Who declares what ​

Containerflatfooter
FormPageLayouttruetrue
FormPageLayout with structuredfalsetrue
FormPageLayout with barefalsefalse
SchemaFormDialogtruefalse
Anything elsefalsefalse

FormPageLayout derives both from its own props: flat is !bare && !structured, footer is !bare. structured is how a page keeps its group headings; bare opts out of the card and the form contract entirely, for a page that is a shell rather than a form.

Example ​

vue
<script setup lang="ts">
import type { FormSurface } from '@core/schema/formSurface'
import { FORM_SURFACE } from '@core/schema/formSurface'
import { provide } from 'vue'

const surface: FormSurface = { flat: true, footer: false }

provide(FORM_SURFACE, surface)
</script>

<template>
  <aside class="w-80 border-s p-4">
    <slot />
  </aside>
</template>

Any SchemaForm rendered into that slot now lays itself out flat and leaves the footer to you.

Notes ​

  • This is the reason eighty-odd form pages share one shape. Without it every page would pass flat and footer props down by hand, and they would drift.
  • A dialog sets footer: false because it owns a DialogFooter. Nesting the two produces two Cancel buttons, which is exactly the bug this prevents.
  • The value is plain, not reactive. A surface is a property of the container, and containers do not change what they are mid-life.
  • useFormSurface falls back to DEFAULT_FORM_SURFACE rather than throwing, so a SchemaForm dropped anywhere still renders.