Default components
ui.components() is the shipped library. It drives s2script_lib.xml in addon 3790153369. Plugins pass data, not panel ids. The pool is host-global so two plugins cannot claim the same panel. Import ui from @s2script/cs2 and call it from OnPluginStart — it throws after settle. Toast, badge, and modal hang off the returned object, not ui itself.
import { ui } from '@s2script/cs2';
export function OnPluginStart(): void {
const panels = ui.components();
} | Kind | Pool | Claim |
|---|---|---|
| Toast | 4, rotating | panels.toast(slot, spec) — no claim, next free line |
| Badge | 4 | panels.badge(spec) — null when exhausted |
| Modal | 2, 8 rows each | panels.modal(spec) — null when exhausted |
Variant is "primary" | "good" | "warn" | "bad" | "ghost". ghost is the low-emphasis default for footer buttons.
Need a kill feed, scoreboard, or MOTD the library does not cover? Mount your own layout.
Toast
Transient notice. No claim — four lines rotate. Drive from any slot-bearing callback (OnClientActive, player_death, a command).
panels.toast(slot, {
title?: string;
message?: string;
variant?: Variant; // default unstyled
holdSeconds?: number; // default 6; 0 keeps it until replaced
}); Returns HudResult (null on success). A generation stamp per line so a replacement is not yanked by the previous hold timer.
Badge
Persistent corner element. Claim once, then show / hide per player.
const badge = panels.badge({
corner?: "tl" | "tr" | "bl" | "br"; // default "tr"
title?: string;
accent?: "accent" | "good" | "warn" | "bad";
});
badge?.show(slot, { title?: string; text?: string });
badge?.hide(slot);
badge?.release(); // return the pool slot show overlays title / text on the spec defaults for that player.
Modal
Paged list. onPick, cursor(), and select() use absolute indices into the full row list, not the current page.
const modal = panels.modal({
title: string | ((slot) => string);
subtitle?: string | ((slot) => string); // default: page indicator when paged
rows: Row[] | ((slot) => Row[]);
onPick?: (slot, index, row) => void;
detail?: (slot, row, cursor) => string[]; // up to 4 lines; last is clamped
buttons?: FooterButton[]; // up to 5
pageSize?: number; // default 8
width?: "sm" | "md" | "lg" | "xl"; // default "md" (560px)
}); Row is { a: string; b?: string; c?: string; disabled?: boolean }. a flexes; b and c are right-aligned. disabled greys the row — onPick still fires so you can say why.
FooterButton is { text: string; variant?: Variant; onClick: (slot) => void }. Prev/Next take the trailing two footer slots automatically when the list pages.
modal?.open(slot);
modal?.close(slot);
modal?.isOpen(slot);
modal?.refresh(slot?); // omit slot → every player who has it open
modal?.page(slot, delta);
modal?.select(slot, index); // absolute; pages to it
modal?.cursor(slot); // absolute index, same space as onPick
modal?.forget(slot);
modal?.release(); Put attacker-controlled text on the last detail line and escape it before it reaches the call.
Shared
hideAll(slot)— hide every pooled panel for one player.forget(slot)— drop per-player state (disconnect).ensure()— force-spawn the library layout. Optional oncecomponents()has run and a client is active.hud— the underlying primitive, for anything the library does not cover.budget()— intern counts forpanelIds,classNames, andvariables. Each vector is its own 1024 cap (not one shared 3072). Interning is idempotent; repaints are free. Past the cap a name is refused and its value never arrives.
See also
- HUD — setup, first draw, screen effects
- Mounting your own
- HUD module