---
title: "Adding a block · Sanity × Nuxt UI"
description: "Turn any Nuxt UI component into a block editors can use, in five steps."
canonical_url: "https://sanity-nuxtui.vercel.app/docs/adding-a-block"
meta:
  "og:description": "Turn any Nuxt UI component into a block editors can use, in five steps."
  "og:image": "https://sanity-nuxtui.vercel.app/_og/d/c_Page%2Ctitle_Adding%2Ba%2Bblock%2Cdescription_~VHVybiBhbnkgTnV4dCBVSSBjb21wb25lbnQgaW50byBhIGJsb2NrIGVkaXRvcnMgY2FuIHVzZSwgaW4gZml2ZSBzdGVwcy4%2Cheadline_Docs%2CsiteName_~U2FuaXR5IMOXIE51eHQgVUk%2Cp_Ii9kb2NzL2FkZGluZy1hLWJsb2NrIg%2Cs_oCe6hllSbBsZFhKf.png"
  "og:image:height": "630"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Adding a block · Sanity × Nuxt UI"
  "twitter:image": "https://sanity-nuxtui.vercel.app/_og/d/c_Page%2Ctitle_Adding%2Ba%2Bblock%2Cdescription_~VHVybiBhbnkgTnV4dCBVSSBjb21wb25lbnQgaW50byBhIGJsb2NrIGVkaXRvcnMgY2FuIHVzZSwgaW4gZml2ZSBzdGVwcy4%2Cheadline_Docs%2CsiteName_~U2FuaXR5IMOXIE51eHQgVUk%2Cp_Ii9kb2NzL2FkZGluZy1hLWJsb2NrIg%2Cs_oCe6hllSbBsZFhKf.png"
---

> Index of every page on this site: [llms.txt](https://sanity-nuxtui.vercel.app/llms.txt)

[Docs](https://sanity-nuxtui.vercel.app/docs)

# **Adding a block**

Turn any Nuxt UI component into a block editors can use, in five steps.

A block is a schema in the Studio and a component on the site, connected by its `_type`. This guide adds a ****Stats**** block: a title and a row of numbers with labels.

## 1. Schema

Create `apps/studio/schemaTypes/blocks/stats.ts`. Name fields after the props of the Nuxt UI component you will use: the component can then pass values straight through, and the Nuxt UI docs double as field documentation.

apps/studio/schemaTypes/blocks/stats.ts

```
import { defineArrayMember, defineField, defineType } from 'sanity'import { ActivityIcon } from '@sanity/icons/Activity'import { anchorField, headerFields } from '../shared/fields' export const stats = defineType({  name: 'stats',  title: 'Stats',  type: 'object',  icon: ActivityIcon,  fields: [    ...headerFields,    defineField({      name: 'items',      title: 'Numbers',      type: 'array',      of: [defineArrayMember({        type: 'object',        fields: [          defineField({ name: 'value', title: 'Value', type: 'string' }),          defineField({ name: 'label', title: 'Label', type: 'string' })        ]      })]    }),    anchorField  ]})
```

`headerFields` adds the headline, title and description every section block has.

## 2. Register the schema

Add the type to `schemaTypes/index.ts`, then to the page builder in `schemaTypes/objects/page-builder.ts`, including a group of the insert menu:

apps/studio/schemaTypes/objects/page-builder.ts

```
of: [  // …  defineArrayMember({ type: 'stats' })],options: {  insertMenu: {    groups: [      { name: 'content', title: 'Content', of: ['section', 'cardGrid', 'timeline', 'imageGallery', 'richText', 'stats'] },      // …    ]  }}
```

## 3. Generate the types

```
pnpm typegen
```

The block is now part of `PageBlock` in `apps/web/sanity.types.ts`, and `BlockOf<'stats'>` gives its exact type.

**Blocks with references need a projection**

When a block links to documents or uses images, add a branch for it in BLOCKS in apps/web/app/utils/queries.ts (see how cardGrid resolves link and image). Plain fields like the ones above come through ... automatically.

## 4. Component

Create `apps/web/app/components/blocks/BlockStats.vue`. Follow the pattern of the existing blocks: `SectionLabel` for the headline, `sectionUi` for the scroll-in animation, and `revealIndex()` to stagger items in a row.

apps/web/app/components/blocks/BlockStats.vue

```
<script setup lang="ts">import { stegaClean } from '@sanity/client/stega'import type { BlockOf } from '~/types/sanity' defineProps<{ block: BlockOf<'stats'>, index: number }>()</script> <template>  <UPageSection    :id="stegaClean(block.anchor)"    :title="block.title"    :description="block.description"    :ui="sectionUi"  >    <template v-if="block.headline" #headline>      <SectionLabel :text="block.headline" :index="index" />    </template>     <UPageGrid>      <UPageCard        v-for="(item, itemIndex) in block.items"        :key="item._key"        :title="item.value"        :description="item.label"        class="reveal"        :style="revealIndex(itemIndex)"      />    </UPageGrid>  </UPageSection></template>
```

## 5. Register the component

Add it to the registry in `apps/web/app/components/PageBlocks.vue`:

apps/web/app/components/PageBlocks.vue

```
const registry: Record<BlockType, Component> = {  hero: LazyBlockHero,  // …  stats: LazyBlockStats}
```

The registry is typed as `Record<BlockType, Component>`, so `pnpm typecheck` fails until every block in the schema has a component. In development, a block without a component shows a warning on the page instead of breaking it.

## Clean values that drive logic

During Visual Editing, strings carry invisible markers (stega) so the Presentation tool knows which field each text comes from. Text displays fine with them, but values used as logic break: icon names, colors, variants, URLs, anchors, class names. Wrap those in `stegaClean()`, as `:id="stegaClean(block.anchor)"` does above. `toButton()` and `toButtons()` in `app/utils/links.ts` do it for links.

[**Content model** The document types, how URLs map to them, and why entries are generic.](https://sanity-nuxtui.vercel.app/docs/content-model) [**Queries and types** How GROQ queries are organized, typed with TypeGen and used in components.](https://sanity-nuxtui.vercel.app/docs/queries-and-types)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://sanity-nuxtui.vercel.app/#website","@type":"WebSite","name":"Sanity × Nuxt UI","url":"https://sanity-nuxtui.vercel.app/","publisher":{"@id":"https://sanity-nuxtui.vercel.app/#identity"}},{"@id":"https://sanity-nuxtui.vercel.app/docs/adding-a-block#webpage","@type":"WebPage","dateModified":"2026-10-16T02:29:18Z","description":"Turn any Nuxt UI component into a block editors can use, in five steps.","name":"Adding a block","url":"https://sanity-nuxtui.vercel.app/docs/adding-a-block","isPartOf":{"@id":"https://sanity-nuxtui.vercel.app/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://sanity-nuxtui.vercel.app/docs/adding-a-block"]}]},{"@id":"https://sanity-nuxtui.vercel.app/#identity","@type":"Organization","name":"Sanity × Nuxt UI","url":"https://sanity-nuxtui.vercel.app"},{"@id":"https://sanity-nuxtui.vercel.app/docs/adding-a-block#article","@type":"Article","dateModified":"2026-10-16T02:29:18Z","description":"Turn any Nuxt UI component into a block editors can use, in five steps.","headline":"Adding a block","inLanguage":"en","thumbnailUrl":"https://sanity-nuxtui.vercel.app/_og/d/c_Page%2Ctitle_Adding%2Ba%2Bblock%2Cdescription_~VHVybiBhbnkgTnV4dCBVSSBjb21wb25lbnQgaW50byBhIGJsb2NrIGVkaXRvcnMgY2FuIHVzZSwgaW4gZml2ZSBzdGVwcy4%2Cheadline_Docs%2CsiteName_~U2FuaXR5IMOXIE51eHQgVUk%2Cp_Ii9kb2NzL2FkZGluZy1hLWJsb2NrIg%2Cs_oCe6hllSbBsZFhKf.png","author":{"@id":"https://sanity-nuxtui.vercel.app/#identity"},"image":{"@id":"https://sanity-nuxtui.vercel.app/#/schema/image/1"},"isPartOf":{"@id":"https://sanity-nuxtui.vercel.app/docs/adding-a-block#webpage"},"mainEntityOfPage":{"@id":"https://sanity-nuxtui.vercel.app/docs/adding-a-block#webpage"},"publisher":{"@id":"https://sanity-nuxtui.vercel.app/#identity"}},{"@id":"https://sanity-nuxtui.vercel.app/#/schema/image/1","@type":"ImageObject","contentUrl":"https://sanity-nuxtui.vercel.app/_og/d/c_Page%2Ctitle_Adding%2Ba%2Bblock%2Cdescription_~VHVybiBhbnkgTnV4dCBVSSBjb21wb25lbnQgaW50byBhIGJsb2NrIGVkaXRvcnMgY2FuIHVzZSwgaW4gZml2ZSBzdGVwcy4%2Cheadline_Docs%2CsiteName_~U2FuaXR5IMOXIE51eHQgVUk%2Cp_Ii9kb2NzL2FkZGluZy1hLWJsb2NrIg%2Cs_oCe6hllSbBsZFhKf.png","inLanguage":"en","url":"https://sanity-nuxtui.vercel.app/_og/d/c_Page%2Ctitle_Adding%2Ba%2Bblock%2Cdescription_~VHVybiBhbnkgTnV4dCBVSSBjb21wb25lbnQgaW50byBhIGJsb2NrIGVkaXRvcnMgY2FuIHVzZSwgaW4gZml2ZSBzdGVwcy4%2Cheadline_Docs%2CsiteName_~U2FuaXR5IMOXIE51eHQgVUk%2Cp_Ii9kb2NzL2FkZGluZy1hLWJsb2NrIg%2Cs_oCe6hllSbBsZFhKf.png"}]}
```

## Sitemap

See the full [sitemap](https://sanity-nuxtui.vercel.app/sitemap.md) for all pages.
