# The block picker (/docs/schema/blocks/picker)

---
title: The block picker
description: "`.admin()` is the only thing standing between an editor and thirty unlabelled block types. It sets the name, the icon and the group each block appears under."
kind: guide
package: "@questpie/admin"
---

A block with no `.admin()` still works. It shows up in the sidebar under
"Other", wearing its type name and a default cube, which is fine for two blocks
and unusable at twenty.

## The options

`.admin()` takes a config object, or a function given `{ c }`, the component
proxy that `c.icon()` comes from. Icons are Iconify names, resolved by the
admin at render time.

| Option        | Type                  | Effect                                   |
| ------------- | --------------------- | ---------------------------------------- |
| `label`       | `I18nText`            | The name in the picker.                  |
| `description` | `I18nText`            | Help text beside it.                     |
| `icon`        | `ComponentReference`  | `c.icon("ph:image")`.                    |
| `category`    | `BlockCategoryConfig` | The group: `{ label, icon?, order? }`.   |
| `order`       | `number`              | Position inside the group, lowest first. |
| `hidden`      | `boolean`             | Keeps the block out of the picker.       |

An `I18nText` can be a plain string or a map of locale to string, so
`label: { en: "Hero", sk: "Hero sekcia" }` is as valid as `label: "Hero"`.

```ts
.admin(({ c }) => ({
	label: { en: "Hero Section", sk: "Hero sekcia" },
	description: { en: "Full-width image with a headline" },
	icon: c.icon("ph:image"),
	category: { label: "Sections", icon: c.icon("ph:layout"), order: 1 },
	order: 1,
}))
```

## How the groups come out

Blocks are grouped by their category label, lowercased. Two blocks naming the
category "Sections" land together whatever else they pass, and the first one
seen supplies the icon and order for the whole group.

Groups sort by the category's `order`, blocks sort by their own inside it, and
anything without an `order` falls back to 999. A block with no `category` at all
joins "Other", which carries 999 as well and lands at the bottom.

Because the label is the key, spell it once and import it.

```ts title="src/questpie/server/blocks/_categories.ts"
import type {
	AdminConfigContext,
	BlockCategoryConfig,
} from "@questpie/admin/factories";

export const sections = (c: AdminConfigContext["c"]): BlockCategoryConfig => ({
	label: "Sections",
	icon: c.icon("ph:layout"),
	order: 1,
});
```

Then every block in the folder says `category: sections(c)` and the group holds
together on its own. A file starting with `_` is not a block, so the helper can
live beside them.

## Retiring a block

`hidden: true` takes a block out of the picker while leaving it registered.
Pages that already use it keep rendering, and nobody can add another one.
Deleting the file instead leaves those pages holding a type nothing answers to:
the editor shows "Unknown block type" and your frontend renders nothing.

## Laying out the fields

Where `.admin()` handles the picker, `.form()` handles the panel an editor sees
after choosing the block. It takes the same layout shapes a collection form
takes, referencing fields by name through the `f` proxy.

```ts
.form(({ f }) => ({
	fields: [
		{ type: "section", label: "Content", fields: [f.title, f.subtitle] },
		{
			type: "section",
			label: "Layout",
			layout: "grid",
			columns: 2,
			fields: [f.alignment, f.height],
		},
	],
}))
```

Without `.form()` the fields render in declaration order, which is often
enough.

## Related

- **[Blocks](/docs/schema/blocks)** for the builder these two methods sit on.
- **[Admin collections](/docs/admin/collections)** for the same layout shapes on
  a collection form.
