🧩 kompozr is a composable UI layer for discord.js bots, providing ergonomic wrapper functions for building Discord UI components with less boilerplate and a focus on developer experience.
- About
- Installation
- Quick Start
- Components & Usage
- Full Example
- Full Example With Reactive Components
- API Reference
- TypeScript Support
- Contributing
- License
kompozr was created to improve the developer experience when building
Discord bots with discord.js.
The Discord.js builder API is powerful, but can be verbose and repetitive for
common UI patterns. kompozr introduces a philosophy of "less builders":
- Compose UIs with fewer lines of code
- Use simple, declarative wrappers instead of chaining builder methods
- Focus on what your UI should do, not how to wire up every builder
kompozr is open source and welcomes contributions! If you have ideas, improvements, or new components, feel free to open a PR or issue on GitHub.
- Developer Experience First: Less boilerplate, more readable code, and a declarative API.
- Composable: Easily combine components and layouts.
- All Discord UI Components: Buttons, select menus (all types), modals, inputs, galleries, files, and more.
- Layout Helpers: Rows, sections, containers, separators, and flexible layouts.
- Type-safe: Written in TypeScript with full type definitions.
- Less Builders Philosophy: No more endless
.addX()
and.setY()
chains—just describe your UI in objects and arrays. - Reactive Utilities: Advanced helpers for stateful, memoized, and reusable UI fragments.
- Open Source: Contributions and PRs are welcome!
npm install kompozr
Import the main API object:
import { k } from "kompozr";
Buttons are interactive components that users can click. With kompozr, you can
easily create all Discord button styles using a simple and consistent API. Each
button requires a cid
(custom id) and a label
or emoji
. You can also add
both and set the button as disabled.
const button = k.button.primary({
cid: "my_button",
label: "Click me!",
emoji: "👋",
});
Other button styles:
k.button.secondary
– Gray button for secondary actions.k.button.success
– Green button for positive actions.k.button.danger
– Red button for destructive actions.k.button.link
– Link button (useurl
instead ofcid
).
const linkButton = k.button.link({
url: "https://github.com/silentadv/kompozr",
label: "GitHub",
});
Select menus allow users to pick one or more options from a dropdown. kompozr
supports all Discord select menu types, including string, role, user, channel,
and mentionable selects. Each select requires a cid
and an array of options.
Default Values:
For select menus that support default values (user, role, channel, mentionable), use thedefaultValues
property and the helpers:
k.selectValue.user(id)
k.selectValue.role(id)
k.selectValue.channel(id)
const selectMenu = k.select.string({
cid: "my_select",
options: [
{ value: "1", label: "Option 1" },
{ value: "2", label: "Option 2" },
],
placeholder: "Choose an option",
});
Other select types:
k.select.role
– Select one or more roles.k.select.user
– Select one or more users.k.select.channel
– Select one or more channels.k.select.mentionable
– Select users or roles.
const channelSelect = k.select.channel({
cid: "channel_select",
placeholder: "Pick a channel",
channelTypes: [ChannelType.GuildText, ChannelType.GuildVoice],
});
const userSelect = k.select.user({
cid: "user_select",
placeholder: "Pick a user",
defaultValues: [
k.selectValue.user("user-id-1"),
k.selectValue.user("user-id-2"),
],
});
Modals are pop-up forms that can collect user input. kompozr lets you build
modals with short or paragraph text inputs. Each input requires a cid
and a
label
. You can mark inputs as required and set placeholders.
const modal = k.modal({
cid: "feedback_modal",
title: "Feedback",
inputs: [
k.input.short({
cid: "username",
label: "Your Name",
required: true,
}),
k.input.paragraph({
cid: "feedback",
label: "Your Feedback",
required: true,
}),
],
});
kompozr provides helpers to organize your UI components into rows, sections, containers, and layouts.
Use row
only to group buttons. Discord allows only one select menu per row,
and kompozr's select wrappers already return a row containing the select menu.
const row = k.row(
k.button.success({ cid: "ok", label: "OK" }),
k.button.danger({ cid: "cancel", label: "Cancel" })
);
Sections let you combine text and an accessory (like a button or select) in a
single block. Use section
to align text (max 3 text display) side by side with
an accessory, such as a button or a thumbnail. The main content can be a plain
string or a component created with k.text
. This makes it easy to display a
message with an interactive element or image next to it.
const section = k.section({
components: ["Welcome to the server!", k.text("Enjoy your stay!")],
accessory: k.button.primary({ cid: "welcome", label: "Say Hi!" }),
});
Separators visually divide content. Choose from different sizes and visibility.
k.separator.small; // small spacing
k.separator.large; // large spacing
k.separator.smallHidden; // small spacing without divider (only space)
k.separator.largeHidden; // large spacing without divider (only space)
Containers are an layout block for messages, is a component composer, allowing you to group sections, rows, separators, and other components. You can also set a color for the container.
const container = k.container({
components: [
section,
k.row(
k.button.success({ cid: "ok", label: "OK" }),
k.button.danger({ cid: "cancel", label: "Cancel" })
),
k.separator.small,
"good text here.",
k.text("line 1", "line 2"),
],
color: "Blurple", // or [114,137,218], #ff0000, 0xff
});
The layout
utility is a simple helper that lets you combine any
components—including plain strings—into a single array. This means you can use
strings directly in your layouts without always needing to wrap them with
k.text
or a text display builder. It's mainly for convenience and advanced
custom layouts, and does not support color like container
.
const layout = k.layout(
k.text("Header"),
k.row(
k.button.primary({ cid: "a", label: "A" }),
k.button.secondary({ cid: "b", label: "B" })
),
k.separator.small,
"Hello World!"
);
kompozr also provides helpers for displaying text, images, files, and media galleries.
Display plain or formatted text in your UI.
const text = k.text("Hello, world!");
const multiLineText = k.text("Line1", "Line2", "Line3");
Add a thumbnail image with an optional description.
const thumbnail = k.thumbnail({
url: "https://example.com/thumb.png",
description: "A thumbnail",
});
Attach a file to your message, with optional spoiler support.
const file = k.file({
url: "https://example.com/file.pdf",
spoiler: true,
});
Display a gallery of images or media files. Each item can have a description and be marked as a spoiler.
const gallery = k.gallery(
{ url: "https://example.com/image1.png", description: "First image" },
{ url: "https://example.com/image2.png", spoiler: true }
);
kompozr also provides a set of reactive utilities for advanced UI composition and state management. These are useful for building dynamic, stateful, or memoized UI fragments in your Discord bot.
- When you want to reuse UI fragments with different props (like React fragments).
- When you need to memoize expensive UI computations and only update when dependencies change.
- When you want to manage local state for a UI component or section.
Creates a reusable UI fragment (like a functional component).
Use when you want to generate repeated or parameterized UI blocks.
Example:
interface User {
id: string;
username: string;
}
// type anotation only is necessary in typescript projects.
// In javascript projects just call k.fragment(...)
const UserSection = k.fragment<User>((user) =>
k.section({
components: [`User: ${user.username}`],
accessory: k.button.primary({ cid: `user_${user.id}`, label: "Select" }),
})
);
// Usage:
const users = [
{ id: "1", username: "Alice" },
{ id: "2", username: "Bob" },
];
const userSections = UserSection(users); // returns an array of sections
Use case:
Reusable UI blocks for lists, cards, or repeated sections.
Memoizes a UI fragment, only recomputing when dependencies change.
Use when you have expensive UI generation logic and want to avoid unnecessary
recomputation.
Example:
interface Props {
value: number;
}
// type anotation only is necessary in typescript projects.
// In javascript projects just use (props) => ... and (props) => [...]
const ExpensiveSection = k.memo(
(props: Props) =>
k.section({
components: [`Value: ${props.value}`],
accessory: k.button.primary({ cid: "btn", label: "Go" }),
}),
(props: Props) => [props.value] // dependencies
);
// Usage:
const section = ExpensiveSection({ value: 42 }); // build
ExpensiveSection({ value: 42 }); // cached
ExpensiveSection({ value: 44 }); // rebuild because dependencies are changed
ExpensiveSection({ value: 44 }); // cached
ExpensiveSection({ value: 42 }); // rebuild because dependencies are changed
Use case:
Performance optimization for dynamic UIs that depend on changing props.
Creates a stateful UI component with local state and an update method.
Use when you want to encapsulate state and rendering logic together.
Example:
const counter = k.stateful({ count: 0 }, (state) =>
k.section({
components: [`Count: ${state.count}`],
accessory: k.button.primary({ cid: "inc", label: "Increment" }),
})
);
// Usage:
counter.render(); // renders with current state
counter.update({ count: counter.state.count + 1 }); // update state
Use case:
Local state management for interactive or dynamic UI sections.
import { k } from "kompozr";
const button = k.button.success({
cid: "ok_btn",
label: "OK",
});
const gallery = k.gallery({
url: "https://example.com/cat.png",
description: "A cute cat",
});
const message = k.container({
components: [
k.section({
components: ["Check out this gallery!"],
acessory: button,
}),
gallery,
k.separator.small,
k.text("Thanks for using kompozr!"),
],
color: "Green",
});
// Send `message` as your bot's response
const UserCard = k.fragment<User>((user) =>
k.section({
components: [`👤 ${user.name}`],
accessory: k.button.primary({ cid: `select_${user.id}`, label: "Select" }),
})
);
const userList: User[] = [
{ id: "1", name: "Alice" },
{ id: "2", name: "Bob" },
];
const message = k.container({
components: [
...UserCard(userList), // list of sections
k.separator.small,
k.text("Select a user!"),
],
});
All builder functions return Discord.js builder instances, ready to be used in your bot's responses.
- Buttons:
k.button.primary
,k.button.secondary
,k.button.success
,k.button.danger
,k.button.link
- Select Menus:
k.select.string
,k.select.role
,k.select.user
,k.select.channel
,k.select.mentionable
- Populated Select Menus Values:
k.selectValue.user
,k.selectValue.role
,k.selectValue.channel
- Modals & Inputs:
k.modal
,k.input.short
,k.input.paragraph
- Layout:
k.row
,k.section
,k.container
,k.separator
,k.layout
- Content:
k.text
,k.thumbnail
,k.gallery
,k.file
- Reactive:
k.memo
,k.stateful
,k.fragment
kompozr is written in TypeScript and ships with full type definitions.
kompozr is open source and contributions are welcome!
If you have suggestions, bug reports, or want to add new features/components,
please open an issue or PR on GitHub.
MIT
Made with ❤️ by primepvi