> ## Documentation Index
> Fetch the complete documentation index at: https://www.chapterjs.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Embeds and layout

> Write an embed once and use it everywhere, and build messages out of texts, sections, galleries and containers

An embed used in several messages is declared with `embed()`, in any file of `src/`, and exported. It is the embed itself, or a function of what it shows.

```ts src/components/welcome.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { embed, type GuildMember } from 'chapterjs';

export default embed((member: GuildMember) => ({
  title: `Welcome ${member.displayName}!`,
  description: `You are member number ${member.guild.memberCount ?? '?'}.`,
  color: 0xc026d3,
  thumbnail: { url: member.avatarURL() },
}));
```

```ts src/events/greet.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event } from 'chapterjs';
import welcome from '../components/welcome';

export default event({
  name: 'memberJoin',
  async run({ member, guild }) {
    const general = [...guild.channels.values()].find(channel => channel.name === 'general');
    if (general?.isText()) await general.send({ embeds: [welcome(member)] });
  },
});
```

That is all: `welcome(member)` returns the embed, checked against the limits of Discord when the message is sent. An embed that always looks the same is given directly, and used without calling it:

```ts src/components/rules.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { embed } from 'chapterjs';

export default embed({
  title: 'Rules',
  description: '1. Be kind.\n2. No spam.\n3. Have fun.',
  color: 0x5865f2,
});
```

```ts src/commands/rules.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';
import rules from '../components/rules';

export default command({
  name: 'rules',
  description: 'Shows the rules',
  async run({ interaction }) {
    await interaction.reply({ embeds: [rules] });
  },
});
```

A static embed is checked when the file loads, so a title over 256 characters is an error in `chapterjs dev` before anything is sent. What an embed can hold is in [Embed](https://www.chapterjs.org/reference/message-options.md#embed).

> [!NOTE]
>
> `embed()` and `EmbedDeclaration` are in the [reference](https://www.chapterjs.org/reference/embed.md); every layout helper of this page is in [Layout](https://www.chapterjs.org/reference/layout.md).

## Messages made of components

Discord can show a message made of components only: texts, sections with a thumbnail or a button beside them, galleries of images, files, separators, and containers with a colored line like an embed. Write them with the helpers of `'chapterjs'`, where the message is:

```ts src/commands/profile.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command, container, linkButton, section, separator, text, thumbnail } from 'chapterjs';

export default command({
  name: 'profile',
  description: 'Shows your profile',
  async run({ interaction, member }) {
    await interaction.reply({
      components: [
        container(
          [
            section(
              [`## ${member.displayName}`, `Joined ${member.joinedAt?.toDateString() ?? 'a while ago'}`],
              thumbnail(member.avatarURL())
            ),
            separator(),
            text(`${member.roles.length} roles`),
            linkButton({ label: 'Open in Discord', url: `https://discord.com/users/${member.id}` }),
          ],
          { color: 0xc026d3 }
        ),
      ],
    });
  },
});
```

As soon as a message has one of these, Discord shows components only: it takes no `content` and no `embeds`, and ChapterJS says so before sending if you pass one. Put the text in `text()`, and the look of an embed in a `container()`.

| Helper | What it is |
| - | - |
| `text(content)` | A text, with Markdown. All the texts of a message add up to 4000 characters. |
| `section(texts, accessory)` | 1 to 3 texts with a button or a `thumbnail()` beside them |
| `thumbnail(url)` | A small picture, beside a section |
| `gallery(items)` | 1 to 10 pictures or videos in a grid |
| `file(name)` | A file sent with the message (`files`), shown where it is placed |
| `separator()` | Space between two pieces, with a line by default |
| `container(children, { color })` | A box around texts, sections, rows, galleries, files and separators |
| `row(components)` | A row of up to 5 buttons, or one menu, when you want to choose the rows yourself |

A picture of a gallery or a thumbnail is a link, or a file of the message with `attachment://name`. The files of your bot live in `public/`, and [`asset()`](https://www.chapterjs.org/messages#files) picks one with the help of your editor:

```ts src/commands/photos.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { asset, command, gallery, text } from 'chapterjs';

export default command({
  name: 'photos',
  description: 'Shows the photos of the week',
  async run({ interaction }) {
    await interaction.reply({
      files: [asset('photos/monday.png')],
      components: [
        text('# This week'),
        gallery(['attachment://monday.png', { url: 'https://example.com/tuesday.png', description: 'The team on Tuesday' }]),
      ],
    });
  },
});
```

A message of buttons and menus only holds 5 rows at most (a menu takes a row by itself). A message built with these pieces holds 40 components at most, rows and the pieces inside containers included. Going over a limit throws before anything is sent, with the limit in the message.

> [card] [Sending messages](https://www.chapterjs.org/messages.md)
>
> Everything a message can contain, and where it goes.

> [card] [Buttons](https://www.chapterjs.org/components.md)
>
> Run code when a button is clicked.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.