⌨️ Keyboard Factory¢

Raito comes with two decorators for building keyboards declaratively:

  • @keyboard.static β€” for simple static layouts

  • @keyboard.dynamic β€” for flexible builder-style keyboards

No need to manually manage builders or markup objects β€” just write declarative logic.


@keyboard.staticΒΆ

Use this when you want to declare layout via return value.

from raito import rt

@rt.keyboard.static(inline=False)
def start_markup():
    return [
        ["πŸ€ Throw a ball"],
        [["πŸ“„ FAQ"], ["πŸ† Leaderboard"]],
    ]

Each item is either:

  • A str or tuple β€” becomes a single button

  • A list of such items β€” becomes a row

Tip

Returns a ReplyKeyboardMarkup or InlineKeyboardMarkup depending on inline= param.


@keyboard.dynamicΒΆ

Use this when you need to build keyboards programmatically.

from aiogram.utils.keyboard import InlineKeyboardBuilder
from raito import rt

@rt.keyboard.dynamic(inline=True)
def faq_markup(builder: InlineKeyboardBuilder, tos_url: str, privacy_url: str):
    builder.button(text="Terms of Service", url=tos_url)
    builder.button(text="Privacy", url=privacy_url)

Tip

You get the builder as the first argument. Everything else is up to you.


ParametersΒΆ

Both decorators accept optional arguments:

  • inline=True β€” whether to build inline or reply keyboard

  • adjust=True β€” whether to auto-adjust layout using builder.adjust()

  • repeat=True β€” if True, adjust(*sizes) will repeat the pattern

  • *sizes β€” pattern for button row sizes (e.g., 2, 2, 1)

@rt.keyboard.dynamic(2, 2, 1, inline=False, repeat=False)
def markup(builder: ReplyKeyboardBuilder):
    ...

ExamplesΒΆ

Static inline keyboard:ΒΆ

from raito import rt

@rt.keyboard.static()
def info_markup():
    return [
        [("πŸ’¬ Support", "support")],
        [("πŸ”’ Privacy", "privacy"), ("πŸ“„ TOS", "terms_of_use")]
    ]

 @router.message(...)
 async def handler(message: Message):
     await message.answer("Buttons:", reply_markup=info_markup())

Static reply keyboard:ΒΆ

from raito import rt

@rt.keyboard.static(inline=False)
def info_markup():
    return [
        ["πŸ’¬ Support"],
        [["πŸ”’ Privacy"], ["πŸ“„ TOS"]]
    ]

@router.message(...)
async def handler(message: Message):
    await message.answer("Buttons:", reply_markup=info_markup())

Dynamic inline keyboard:ΒΆ

from aiogram.utils.keyboard import InlineKeyboardBuilder
from raito import rt

@rt.keyboard.dynamic(1, 2)
def info_markup(builder: InlineKeyboardBuilder, privacy_url: str, tos_url: str):
    builder.button(text="πŸ’¬ Support", callback_data="support")
    builder.button(text="πŸ”’ Privacy", url=privacy_url)
    builder.button(text="πŸ“„ TOS", url=tos_url)

 @router.message(...)
 async def handler(message: Message):
     await message.answer("Buttons:", reply_markup=info_markup(
         privacy_url="https://example.com/privacy",
         tos_url="https://example.com/tos",
     ))

Dynamic reply keyboard:ΒΆ

from aiogram.utils.keyboard import ReplyKeyboardBuilder
from raito import rt

@rt.keyboard.dynamic(1, 2, inline=False)
def info_markup(builder: ReplyKeyboardBuilder):
    builder.button(text="πŸ’¬ Support")
    builder.button(text="πŸ”’ Privacy")
    builder.button(text="πŸ“„ TOS")

@router.message(...)
async def handler(message: Message):
    await message.answer("Buttons:", reply_markup=info_markup())

Custom adjust:ΒΆ

from aiogram.utils.keyboard import InlineKeyboardBuilder
from raito import rt

@rt.keyboard.dynamic(adjust=False)
def admin_markup(builder: InlineKeyboardBuilder, show_balance_management: bool = False):
    adjust = []

    builder.button(text="πŸ‘€ Users", callback_data="users")
    adjust.append(1)

    if show_balance_management:
        builder.button(text="πŸ“€ Withdraw", callback_data="withdraw")
        builder.button(text="πŸ“₯ Deposit", callback_data="deposit")
        adjust.append(2)

    builder.adjust(*adjust)

 @router.message(...)
 async def handler(message: Message):
     await message.answer("Buttons:", reply_markup=admin_markup(True))