User interface
Plugins do not draw anything themselves. They return descriptions of UI, and PhoenixCord renders them with Discord’s own components and theme.
Elements
Section titled “Elements”An element is a tree of nodes. WIT cannot express recursive types, so the tree is stored flat: nodes[0] is the root and each node lists its children by index. In Rust, build a View and flatten it:
use phoenixcord_sdk::View;use phoenixcord_sdk::types::{Color, ColorToken, Direction, Icon, Style, TextSize};
let badge = View::stack(vec![ View::icon(Icon::Named("clock".into())), View::text("3s late"),]).style(Style { direction: Some(Direction::Row), gap: Some(4.0), foreground: Some(Color::Token(ColorToken::TextMuted)), text_size: Some(TextSize::Xs), ..Style::default()}).tooltip("Message latency").into_element();Node kinds are stack, text, icon, image, button, toggle, text-input, select, link, code, divider and spinner. Use color-tokens instead of raw colors so plugin UI follows the user’s theme. Icons are either named icons from Discord’s icon set or raw svg markup, where currentColor follows the node’s foreground color.
UI actions
Section titled “UI actions”Interactive nodes, menu items and buttons carry an action string. When the user interacts, the plugin receives event.action with that string and, for toggles, inputs and selects, the new value:
fn on_event(&mut self, event: Event) { if let Event::Action(action) = event && action.action == "toggle" { self.enabled = !self.enabled; host::request_render(); }}Chat bar buttons
Section titled “Chat bar buttons”fn chat_bar_buttons(&mut self, context: ChatBarContext) -> Vec<ChatBarButton> { if !context.main_chat { return Vec::new(); } vec![ChatBarButton { tooltip: "Toggle".into(), icon: Icon::Named("bell".into()), action: "toggle".into(), active: self.enabled, }]}Context menus
Section titled “Context menus”context-menu receives a menu-target: a user, message, channel, guild, role, image, link or the message input. Return items to add to that menu. Items with a parent index form a submenu under that item.
fn context_menu(&mut self, target: MenuTarget) -> Vec<MenuItem> { let MenuTarget::User(user) = target else { return Vec::new(); }; vec![MenuItem { label: "Copy User URL".into(), action: format!("copy-user-url:{}", user.user_id), icon: Some(Icon::Named("link".into())), checked: None, danger: false, parent: None, }]}Profiles, member list and server list
Section titled “Profiles, member list and server list”| Hook | Returns |
|---|---|
profile-sections |
Elements added to a user profile |
user-badges |
badges shown on a profile |
member-decorations |
Elements next to a user in the member list and DM list |
server-list-elements |
Elements added to the server list |
Toasts, notices, notifications and modals
Section titled “Toasts, notices, notifications and modals”| Function | Shows |
|---|---|
host::show_toast |
A short message at the bottom of the window |
host::show_notice |
A bar at the top of the client, with an optional button |
host::notify |
A desktop notification |
host::open_modal |
A modal containing an element. Update it with update_modal, close it with close_modal |