> ## Documentation Index
> Fetch the complete documentation index at: https://poly-bot.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Keyboards: Fluent Builders and Source-Generated Layouts

> Build keyboards with fluent builders or declare layouts via [CallbackButton] on partial methods — the source generator emits the markup body for you.

PolyBot provides two approaches to constructing Telegram keyboards: a chainable fluent API for runtime-assembled markups, and a declarative source-generated approach where you annotate a `partial` method with `[CallbackButton]` attributes and the generator emits the implementation. Both produce standard `Telegram.Bot` markup types.

**Fluent builders**

Fluent builders accumulate buttons linearly and then shape them into rows with `Build()` (one button per row) or `Adjust(rowSizes)` (explicit row widths, last size repeated for remaining buttons).

### `InlineKeyboardBuilder`

Flat methods cover every button kind — callback, URL (`string` or `Uri`), copy-text, Web App, switch-inline-query variants, login, and pay — plus `Add(...)` for pre-built `InlineKeyboardButton`s. `Row(...)` composes an explicit row (via a nested `InlineKeyboardRowBuilder` or pre-built buttons); composed rows precede the flat buttons in `Build()`.

```csharp theme={null}
using PolyBot.Keyboards;

// One button per row
InlineKeyboardMarkup linear = new InlineKeyboardBuilder()
    .CallbackButton("Open", "open")
    .CallbackButton("Close", "close")
    .Build();

// Two buttons on the first row, one on the second
InlineKeyboardMarkup twoByOne = new InlineKeyboardBuilder()
    .CallbackButton("Yes", "confirm:yes")
    .CallbackButton("No", "confirm:no")
    .CallbackButton("Cancel", "confirm:cancel")
    .Adjust(2, 1);

// Adjust with a single size repeats for all remaining rows
InlineKeyboardMarkup grid = new InlineKeyboardBuilder()
    .CallbackButton("A", "a")
    .CallbackButton("B", "b")
    .CallbackButton("C", "c")
    .CallbackButton("D", "d")
    .CallbackButton("E", "e")
    .Adjust(2, 1, 2); // rows of 2, 1, 2 → [A B] [C] [D E]

// Explicit rows: [Yes No] [Docs] — composed rows come first, then flat buttons
InlineKeyboardMarkup confirm = new InlineKeyboardBuilder()
    .Row(row => row
        .CallbackButton("Yes", "confirm:yes")
        .CallbackButton("No", "confirm:no"))
    .Row(InlineKeyboardButton.WithUrl("Docs", "https://example.com"))
    .CallbackButton("Cancel", "confirm:cancel")
    .Build();
```

> [!NOTE]
>
> `Adjust(...)` chunks only the flat buttons; composed rows are included by `Build()`.

### `ReplyKeyboardBuilder`

`ReplyKeyboardBuilder` mirrors the inline builder and always sets `ResizeKeyboard = true` on the produced markup. Beyond plain text buttons it covers the request-style buttons: contact, location, poll, users, chat, managed business bot, and Web App.

```csharp theme={null}
using PolyBot.Keyboards;

ReplyKeyboardMarkup twoColumns = new ReplyKeyboardBuilder()
    .Button("Morning")
    .Button("Afternoon")
    .Button("Evening")
    .Adjust(2);
// → [Morning  Afternoon]
//   [Evening]
// ResizeKeyboard = true

ReplyKeyboardMarkup share = new ReplyKeyboardBuilder()
    .ContactButton("Share contact")
    .LocationButton("Share location")
    .PollButton("Create poll", new KeyboardButtonPollType { Type = PollType.Quiz })
    .RequestUsersButton("Pick admins", new KeyboardButtonRequestUsers { RequestId = 1, MaxQuantity = 3 })
    .WebAppButton("Open app", webAppInfo)
    .Adjust(2);
```

**Generated keyboards**

Annotate a `partial` method returning `InlineKeyboardMarkup` with one or more `[CallbackButton]` attribute lists. The source generator emits the method body; each **attribute list** (line) forms one row, each **attribute within a list** forms one column button.

```csharp theme={null}
public sealed partial class MyHandlers
{
    // Row 1: [First] [Second]
    // Row 2: [Cancel] [Apply]
    [CallbackButton("First",  "first:{val}"),  CallbackButton("Second", "second:{val}")]
    [CallbackButton("Cancel", "cancel"),        CallbackButton("Apply",  "apply:{val}")]
    private static partial InlineKeyboardMarkup ActionsKeyboard(int val);
}
```

The `{val}` placeholder in `CallbackData` is replaced with the method parameter at runtime:

```csharp theme={null}
InlineKeyboardMarkup kb = MyHandlers.ActionsKeyboard(5);
// rows[0][0].CallbackData == "first:5"
// rows[1][1].CallbackData == "apply:5"
```

**C# 13 partial properties** are also supported when there are no parameters (no placeholders):

```csharp theme={null}
public sealed partial class MyHandlers
{
    [CallbackButton("Yes", "yes"), CallbackButton("No", "no")]
    private static partial InlineKeyboardMarkup ConfirmKeyboard { get; }
}
```

### Using a generated keyboard in a handler

```csharp theme={null}
public sealed partial class BotHandlers
{
    [CallbackButton("Help", "help"), CallbackButton("About", "about")]
    private static partial InlineKeyboardMarkup StartKeyboard();

    [MessageHandler]
    [Command(Aliases = ["start"], Description = "Start the bot")]
    public static async Task<Result> Start(Message msg, ITelegramBotClient bot, CancellationToken ct)
    {
        await bot.SendMessage(
            msg.Chat.Id,
            "Welcome! Choose an option:",
            replyMarkup: StartKeyboard(),
            cancellationToken: ct);

        return Result.Handled();
    }
}
```

## `Adjust` row-sizing rules

`Adjust(params int[] rowSizes)` chunks the flat button list into rows using the given widths in order. When the button list is longer than the specified sizes, the **last size is repeated** for all remaining buttons.

```csharp theme={null}
// 6 buttons, sizes [2, 1] → rows of 2, 1, 1, 1, 1 (last size 1 repeated)
new InlineKeyboardBuilder()
    .CallbackButton("A", "a")
    .CallbackButton("B", "b")
    .CallbackButton("C", "c")
    .CallbackButton("D", "d")
    .CallbackButton("E", "e")
    .CallbackButton("F", "f")
    .Adjust(2, 1);
// → [A B] [C] [D] [E] [F]
```

## `{parameter}` placeholders in generated keyboards

Placeholders in `CallbackData` strings reference the declaring method's parameters by name and are interpolated in the generated code at call time. Placeholders that don't match any parameter name produce warning `CUR017`.

```csharp theme={null}
// val is interpolated; unknown {extra} would produce CUR017
[CallbackButton("Item", "item:{val}:{extra}")]
private static partial InlineKeyboardMarkup ItemKeyboard(int val);
```

> [!NOTE]
>
> Partial properties have no parameters, so `{parameter}` placeholders only work on methods.

## Requirements for generated keyboards

* The containing type **must be `partial`**.
* The method or property return type must be `InlineKeyboardMarkup`; any other type is a compile-time error (`CUR018`).
* Non-`partial` members and generic methods produce warning `CUR019` and are skipped.

## Diagnostics reference

> **CUR017 – Unknown placeholder in CallbackData**
>
> **Severity:** Warning
>
> A `{name}` placeholder in a `[CallbackButton]` attribute's `CallbackData` does not match any parameter of the declaring method. The placeholder will be emitted as a literal string at runtime.

> **CUR018 – Non-InlineKeyboardMarkup return type**
>
> **Severity:** Error
>
> A `[CallbackButton]`-annotated method or property does not return `InlineKeyboardMarkup`. The generator skips the member.

> **CUR019 – Non-partial or generic member**
>
> **Severity:** Warning
>
> A `[CallbackButton]`-annotated member is not declared `partial`, or the method itself is generic. The generator cannot emit an implementation and skips the member.

> **CUR050 – callback\_data literal is invalid**
>
> **Severity:** Error
>
> A constant `callback_data` string passed to `InlineKeyboardButton.WithCallbackData(...)`, `InlineKeyboardBuilder.CallbackButton(...)`, or an `InlineKeyboardButton` object initializer is empty or longer than 64 bytes. The Bot API requires 1–64 bytes, so the button fails at runtime.

> **CUR055 – callback\_data built by string interpolation**
>
> **Severity:** Info
>
> A `callback_data` argument (same call sites as CUR050) is an interpolated string (`$"item:{id}"`). Consider a `[CallbackButton]`-generated keyboard with placeholders, or `[Pattern]` routing with `CallbackPatternMatcher` for typed, length-checked parsing.


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