Skip to content

Buttons and inputs ​

Every control a Twilight Moon screen uses to collect a value from the player or staff: buttons, text fields, selects, toggles, sliders, pickers, knobs and faders. All of them use v-model and follow the server's theme.

KButton ​

The standard button, with seven variants, four sizes, optional icons and a built in busy state.

KButton
KButton sizes, icons and states

A button with no label and no default slot content renders as a square icon button.

PropTypeDefaultDescription
labelstringnoneButton text. The default slot replaces it.
iconstringnoneIcon before the text, for example lucide:save.
iconRightstringnoneIcon after the text.
variant'primary' | 'secondary' | 'ghost' | 'danger' | 'success' | 'warning' | 'subtle-danger''secondary'Visual style.
size'xs' | 'sm' | 'md' | 'lg''md'Height and text size.
blockbooleanfalseStretch to the full width of the parent.
align'center' | 'start''center'Content alignment. start only applies together with block.
disabledbooleanfalseBlocks clicks and dims the button.
loadingbooleanfalseShows a spinner and blocks clicks.
activebooleanfalseShows the pressed look, for toggle style buttons.
titlestringnoneNative tooltip, useful on icon only buttons.
action() => unknownnoneAsync handler. The button shows a spinner and ignores clicks until the returned promise settles. When set, the click event is not emitted.

Events: click (the mouse event). Slots: default (replaces label).

vue
<KButton variant="primary" icon="lucide:save" label="Save" @click="save" />
<KButton :action="submitReport" label="Submit" />
<KButton icon="lucide:x" title="Close" variant="ghost" @click="close" />

KInput ​

A single line text, number or password field with label, hint, error text and optional icon, prefix and suffix.

KInput
PropTypeDefaultDescription
modelValuestring | number | nullnoneThe value. Number inputs emit null when empty.
labelstringnoneLabel above the field.
hintstringnoneHelp text under the field.
errorstringnoneError text under the field, with a red ring.
invalidbooleanfalseRed ring without a message, for compact rows (pair it with a KTooltip).
placeholderstringnonePlaceholder text.
type'text' | 'number' | 'password''text'Input type.
autocompletestringnonePassed to the input element.
iconstringnoneIcon inside the field.
prefixstringnoneFixed text before the value.
suffixstringnoneFixed text after the value.
minnumbernoneMinimum, for number inputs.
maxnumbernoneMaximum, for number inputs.
stepnumber | stringnoneStep, for number inputs.
size'sm' | 'md''md'Field height.
clearablebooleanfalseShows a clear button when the field has a value.
disabledbooleanfalseDisables the field.
requiredbooleanfalseMarks the field as required.
autofocusbooleanfalseFocuses the field when it mounts.
monobooleanfalseMonospace font, for codes and plates.

Events: update:modelValue, enter (Enter key pressed). Exposed: focus().

vue
<KInput v-model="name" label="Full name" icon="lucide:user" clearable @enter="submit" />
<KInput v-model="amount" type="number" prefix="$" :min="0" :step="50" />

KTextarea ​

A multi line text field with label, hint and error text.

KTextarea
PropTypeDefaultDescription
modelValuestring | nullnoneThe text.
labelstringnoneLabel above the field.
hintstringnoneHelp text under the field.
errorstringnoneError text under the field.
placeholderstringnonePlaceholder text.
rowsnumber3Visible rows.
monobooleanfalseMonospace font.
disabledbooleanfalseDisables the field.
readonlybooleanfalseShows the text but blocks edits.

Events: update:modelValue.

vue
<KTextarea v-model="notes" label="Case notes" :rows="4" />

KSelect ​

A dropdown for picking one option. It gets a search box automatically when there are more than 8 options.

KSelect

The SelectOption type is exported from the kit:

ts
interface SelectOption {
  label: string
  value: string | number
  icon?: string
  description?: string
  disabled?: boolean
}
PropTypeDefaultDescription
modelValuestring | number | nullnoneThe selected option's value.
optionsSelectOption[]requiredThe choices.
labelstringnoneLabel above the field.
hintstringnoneHelp text under the field.
placeholderstringnoneText shown when nothing is selected.
emptyTextstringnoneText shown when a search matches nothing.
clearablebooleanfalseAdds an option to clear the selection.
searchablebooleanautomaticShow the search box. On by default when there are more than 8 options.
disabledbooleanfalseDisables the select.
size'sm' | 'md''md'Field height.

Events: update:modelValue, change (fires with the new value when the player picks an option).

vue
<script setup lang="ts">
import { KSelect, type SelectOption } from '@/kit'
const options: SelectOption[] = [
  { label: 'Police', value: 'police', icon: 'lucide:shield' },
  { label: 'Mechanic', value: 'mechanic', icon: 'lucide:wrench' }
]
</script>

<template>
  <KSelect v-model="job" :options="options" label="Job" clearable @change="onJob" />
</template>

KCheckbox ​

A small checkbox for table rows, bulk selection and permission grids. For a labeled on/off setting use KToggle instead.

KCheckbox
PropTypeDefaultDescription
modelValuebooleanrequiredChecked state.
indeterminatebooleanfalseShows a dash while the box is off, for a "some selected" header box.
disabledbooleanfalseDisables the box.

Events: update:modelValue. Clicks do not bubble, so a checkbox can sit inside a clickable row.

vue
<KCheckbox :model-value="allSelected" :indeterminate="someSelected" @update:model-value="selectAll" />
<KCheckbox v-model="row.selected" />

KToggle ​

A switch for an on/off setting, with an optional label and description.

KToggle
PropTypeDefaultDescription
modelValuebooleanrequiredOn or off.
labelstringnoneText next to the switch.
descriptionstringnoneSmaller help text under the label.
disabledbooleanfalseDisables the switch.
boxedbooleanfalseWraps the switch in a bordered card.

Events: update:modelValue.

vue
<KToggle v-model="duty" label="On duty" description="Appear in the dispatch list" />

KSlider ​

A range slider with a colored fill track and a live value readout.

KSlider

Tones are neutral, blue, green, red, amber, violet and teal.

PropTypeDefaultDescription
modelValuenumberrequiredCurrent value.
minnumber0Lowest value.
maxnumber100Highest value.
stepnumber1Increment. Decimals in the step set the readout precision.
labelstringnoneLabel above the track.
iconstringnoneIcon next to the label.
hintstringnoneHelp text under the track.
suffixstringnoneUnit appended to the readout, for example %.
originnumbernoneFill from this value instead of the left end (for a slider that runs from -1 to 1, fill from 0).
toneTone'blue'Fill color.
disabledbooleanfalseDisables the slider.
showValuebooleantrueShow the numeric readout.

Events: update:modelValue (fires on every change while dragging).

vue
<KSlider v-model="volume" label="Volume" icon="lucide:volume-2" suffix="%" />
<KSlider v-model="balance" :min="-1" :max="1" :step="0.1" :origin="0" />

KSegmented ​

A row of buttons where exactly one option is selected, for short lists like view modes or tabs inside a form.

KSegmented
PropTypeDefaultDescription
modelValuestring | number | boolean | nullrequiredThe selected option's value.
optionsArray<{ label: string, value: string | number | boolean, icon?: string, disabled?: boolean }>requiredThe choices.
labelstringnoneLabel above the control.
hintstringnoneHelp text under the control.
size'sm' | 'md''md'Control height.
blockbooleanfalseStretch to the full width of the parent.
disabledbooleanfalseDisables every option.

Events: update:modelValue.

vue
<KSegmented
  v-model="view"
  :options="[
    { label: 'Grid', value: 'grid', icon: 'lucide:layout-grid' },
    { label: 'List', value: 'list', icon: 'lucide:list' }
  ]"
/>

KChips ​

A set of toggleable chips for picking several values. It adds All and None buttons, and a filter box when there are more than 12 options.

KChips
PropTypeDefaultDescription
modelValueArray<string | number>requiredThe selected values.
optionsArray<{ label: string, value: string | number, icon?: string }>requiredThe available chips.
labelstringnoneLabel above the chips.
hintstringnoneHelp text under the chips.
disabledbooleanfalseDisables every chip.
searchablebooleanautomaticShow the filter box. On by default when there are more than 12 options.

Events: update:modelValue. All and None act only on the chips currently visible, so a filter narrows them.

vue
<KChips v-model="days" :options="dayOptions" label="Active days" />

KDatePicker ​

A calendar popup that edits a plain YYYY-MM-DD string. It draws its own calendar because the browser's native date popup does not open reliably in the game's interface.

KDatePicker
PropTypeDefaultDescription
modelValuestring''The date as YYYY-MM-DD, or an empty string.
labelstringnoneLabel above the field.
hintstringnoneHelp text under the field.
errorstringnoneError text under the field.
placeholderstringnoneText shown when empty.
clearablebooleanfalseAdds a clear button.
disabledbooleanfalseDisables the field.
size'sm' | 'md''md'Field height.
minstringnoneEarliest pickable day, YYYY-MM-DD.
maxstringnoneLatest pickable day, YYYY-MM-DD.
weekStart0 | 10First day of the week: 0 is Sunday, 1 is Monday.

Events: update:modelValue, change (both fire with the new date string).

vue
<KDatePicker v-model="born" label="Date of birth" :week-start="1" max="2026-12-31" clearable />

KColorPicker ​

A color field with a saturation pad, hue slider, hex and RGB entry, quick swatches and optional opacity.

KColorPicker
PropTypeDefaultDescription
modelValuestring | nullnone#rrggbb, or #rrggbbaa when alpha is on. Anything else counts as no color.
labelstringnoneLabel above the field.
hintstringnoneHelp text under the field.
placeholderstringnoneText shown when there is no color.
alphabooleanfalseAdds an opacity slider. The value gains two digits while opacity is below 100%.
swatchesstring[]12 common colorsQuick pick colors under the pad.
clearablebooleanfalseAdds a clear button.
disabledbooleanfalseDisables the field.
size'sm' | 'md''md'Field height.

Events: update:modelValue (fires live while dragging), change (fires once when a pick is committed, so bind to it for anything expensive), clear.

vue
<KColorPicker v-model="accent" label="Accent color" alpha clearable @change="saveColor" />

KKnob ​

A rotary knob. Drag up or down (hold Shift for fine steps), use the mouse wheel or arrow keys, and double click to reset.

KKnob
PropTypeDefaultDescription
modelValuenumberrequiredCurrent value.
minnumber0Lowest value.
maxnumber100Highest value.
stepnumber1Increment.
originnumberminWhere the arc starts. A bipolar knob fills out from its center.
defaultValuenumberoriginValue restored by double click and the Home key.
labelstringnoneLabel under the knob.
suffixstringnoneUnit appended to the readout.
displaystringnoneReplaces the value readout (for example KILL).
toneTone'violet'Arc color.
size'sm' | 'md' | 'lg''md'Knob size.
disabledbooleanfalseDisables the knob.
showValuebooleantrueShow the readout.
sensitivitynumber170Pixels of drag for the full range.

Events: update:modelValue (fires on every change while dragging, so debounce upstream when each change costs a network message).

vue
<KKnob v-model="pan" label="Pan" :min="-50" :max="50" :origin="0" tone="teal" />

KFader ​

A vertical or horizontal fader for volume, pitch or a crossfader. Drag or click the track, use the mouse wheel or arrow keys, and double click to reset.

KFader
PropTypeDefaultDescription
modelValuenumberrequiredCurrent value.
minnumber0Lowest value.
maxnumber100Highest value.
stepnumber1Increment.
orientation'vertical' | 'horizontal''vertical'Direction of the track.
lengthnumber130Track length in pixels.
originnumberminWhere the fill starts.
detentbooleanfalseDraw a tick at origin, for a bipolar fader.
invertbooleanfalseVertical only: puts max at the bottom instead of the top (a pitch fader with plus at the bottom).
defaultValuenumberoriginValue restored by double click and the Home key.
labelstringnoneLabel next to the fader.
suffixstringnoneUnit appended to the readout.
displaystringnoneReplaces the value readout.
toneTone'violet'Fill color.
disabledbooleanfalseDisables the fader.
showValuebooleantrueShow the readout.

Events: update:modelValue (fires on every change while dragging, so debounce upstream when each change costs a network message).

vue
<KFader v-model="level" label="Level" suffix="%" />
<KFader v-model="cross" orientation="horizontal" :min="-100" :max="100" :origin="0" detent :length="260" />