@sejtax/lui
v1.0.10
Published
Compact Vue 3 UI library for settings screens, admin panels, overlays, and small app shells.
Maintainers
Readme
lui
lui is a compact Vue 3 UI library for settings screens, admin panels, overlays, and small app shells.
It is intentionally small:
- reusable product-facing primitives
- muted slate-leaning visual direction
- flat public API
- minimal dependencies
- no form framework
- no heavy widgets without a real use-case
What It Includes
Current component set:
- actions and inputs:
UiButton,UiInput,UiTextarea - form controls:
UiCheckbox,UiSwitch,UiRadioGroup,UiField - surfaces and structure:
UiCard,UiPanel,UiDivider - overlays:
UiDialog,UiDropdown,UiContextMenu,UiPopover,UiTooltip - feedback:
UiToast,UiToastViewport,UiAlert - navigation:
UiTabs - status:
UiBadge
Current composables:
useClickOutsideuseEscapeuseControllableStateuseScrollLockuseOverlayPositionuseToastuseFocusTrapuseRovingTabindex
Directional exports at 1.0.4:
UiEmptyState- app-shell contracts from
@sejtax/lui/app
Installation
From npm:
npm install @sejtax/luiFor local development with a sibling repository:
{
"dependencies": {
"@sejtax/lui": "file:../lui"
}
}In a monorepo:
{
"dependencies": {
"@sejtax/lui": "workspace:*"
}
}From GitHub by release tag:
{
"dependencies": {
"@sejtax/lui": "github:sejta/lui#v1.0.4"
}
}Recommended usage:
- use
npm install @sejtax/luifor most projects - use
file:../luionly for local development - use
workspace:*if app and library live in one monorepo - use a GitHub tag for reproducible deploys without npm
Then run npm install in the frontend project as usual.
Basic Usage
Recommended import pattern:
import '@sejtax/lui/style.css'
import { UiButton, UiInput, UiPanel } from '@sejtax/lui'@sejtax/lui root export also imports styles internally, but explicit @sejtax/lui/style.css is the cleaner consumer setup because it makes style loading obvious.
Example:
import { defineComponent, h, ref } from 'vue'
import '@sejtax/lui/style.css'
import { UiButton, UiInput, UiPanel } from '@sejtax/lui'
export default defineComponent({
setup() {
const name = ref('')
return () =>
h(UiPanel, { title: 'Profile' }, {
default: () => [
h(UiInput, {
modelValue: name.value,
placeholder: 'Alex Mercer',
'onUpdate:modelValue': (value: string) => {
name.value = value
},
}),
h(UiButton, null, () => 'Save'),
],
})
},
})Styling And Theme
lui ships with global CSS tokens and utility styles.
The default accent direction is a restrained slate-blue palette tuned for flat controls, settings screens, and admin UI.
Theme switching is currently attribute-based:
document.documentElement.dataset.luiTheme = 'light'
document.documentElement.dataset.luiTheme = 'dark'Available themes right now:
lightdark
Style entry:
@sejtax/lui/style.css
Overlay Components
Overlay family uses the same positioning foundation.
UiDropdown: trigger-based menuUiContextMenu: right-click menu from cursor positionUiPopover: compact floating contentUiTooltip: short non-interactive hintUiDialog: modal surface
The shared positioning layer is exposed through:
useOverlayPosition
Toast Usage
Toast is intentionally minimal: no promise API, no notification framework.
Mount one viewport near the app root:
import { UiToastViewport } from '@sejtax/lui'h('div', [
h(UiToastViewport),
h(AppRoot),
])Trigger toasts programmatically:
import { showToast } from '@sejtax/lui'
showToast({
type: 'success',
title: 'Changes saved',
description: 'Project settings were updated successfully.',
})With an action button:
showToast({
type: 'info',
title: 'Draft deleted',
action: {
label: 'Undo',
onClick: () => restoreDraft(),
},
})Clicking the action button calls onClick and dismisses the toast automatically.
Available toast types:
successinfowarningerror
Toast helpers:
showToast(...)dismissToast(id)clearToasts()useToast()
Field And Form Controls
Use UiField as the common wrapper for:
- label
- description
- error
- control slot
Typical pattern:
h(UiField, {
label: 'Release rules',
description: 'Confirm before publishing.',
error: approved.value ? '' : 'You must confirm first.',
}, {
default: ({ controlId, labelledBy, describedBy, invalid }) =>
h(UiCheckbox, {
id: controlId,
modelValue: approved.value,
invalid: Boolean(invalid),
'aria-labelledby': labelledBy,
'aria-describedby': describedBy,
'onUpdate:modelValue': (value: boolean) => {
approved.value = value
},
}, () => 'I reviewed the checklist'),
})Component Cheatsheet
Quick reminders for the most common primitives.
UiButton
h(UiButton, null, () => 'Primary')
h(UiButton, { variant: 'secondary' }, () => 'Secondary')
h(UiButton, { variant: 'ghost', size: 'sm' }, () => 'Ghost')
h(UiButton, { loading: true }, () => 'Saving...')When loading is true the button is disabled, shows a spinner, and sets aria-busy="true". The label text is hidden visually but the slot content stays in the DOM so the button width does not change.
UiInput
h(UiInput, {
modelValue: email.value,
placeholder: '[email protected]',
'onUpdate:modelValue': (value: string) => {
email.value = value
},
})UiTextarea
h(UiTextarea, {
modelValue: notes.value,
rows: 4,
'onUpdate:modelValue': (value: string) => {
notes.value = value
},
})UiCheckbox
h(UiCheckbox, {
modelValue: enabled.value,
'onUpdate:modelValue': (value: boolean) => {
enabled.value = value
},
}, () => 'Enable notifications')UiSwitch
h(UiSwitch, {
modelValue: compactMode.value,
'onUpdate:modelValue': (value: boolean) => {
compactMode.value = value
},
}, () => 'Compact mode')UiRadioGroup
h(UiRadioGroup, {
modelValue: appearance.value,
items: [
{ label: 'System', value: 'system' },
{ label: 'Light', value: 'light' },
{ label: 'Dark', value: 'dark' },
],
'onUpdate:modelValue': (value: string) => {
appearance.value = value
},
})UiField
h(UiField, {
label: 'Workspace name',
description: 'Shown across the workspace.',
}, {
default: ({ controlId, labelledBy, describedBy }) =>
h(UiInput, {
id: controlId,
'aria-labelledby': labelledBy,
'aria-describedby': describedBy,
modelValue: name.value,
'onUpdate:modelValue': (value: string) => {
name.value = value
},
}),
})UiCard And UiPanel
h(UiCard, null, () => 'Compact grouped content')
h(UiPanel, { title: 'Settings' }, {
default: () => h('p', null, 'Panel content'),
})UiTabs
h(UiTabs, {
items: [
{ label: 'General', value: 'general' },
{ label: 'Activity', value: 'activity' },
],
modelValue: activeTab.value,
'onUpdate:modelValue': (value: string) => {
activeTab.value = value
},
})UiDialog
h(UiDialog, {
open: dialogOpen.value,
title: 'Delete draft',
'onUpdate:open': (value: boolean) => {
dialogOpen.value = value
},
}, {
default: () => h('p', null, 'This action cannot be undone.'),
actions: ({ close }) => [
h(UiButton, { variant: 'ghost', onClick: close }, () => 'Cancel'),
h(UiButton, { onClick: close }, () => 'Delete'),
],
})UiDropdown
h(UiDropdown, {
items: [
{ label: 'Open', value: 'open' },
{ label: 'Duplicate', value: 'duplicate' },
{ type: 'separator' },
{ label: 'Archive', value: 'archive' },
],
onSelect: (item) => {
console.log(item.value)
},
}, {
trigger: () => h(UiButton, { variant: 'secondary' }, () => 'Actions'),
})UiContextMenu
h(UiContextMenu, {
items: [
{ label: 'Open', value: 'open' },
{ label: 'Rename', value: 'rename' },
{ type: 'separator' },
{ label: 'Delete', value: 'delete', disabled: true },
],
onSelect: (item) => {
console.log(item.value)
},
}, {
default: () => h('div', { style: 'padding: 16px;' }, 'Right-click here'),
})UiPopover
h(UiPopover, null, {
trigger: () => h(UiButton, { variant: 'secondary' }, () => 'Open details'),
default: ({ close }) => [
h('h3', null, 'Project rules'),
h('p', null, 'Keep names stable and reusable.'),
h(UiButton, { size: 'sm', onClick: close }, () => 'Close'),
],
})UiTooltip
h(UiTooltip, { content: 'Refresh project metrics' }, {
trigger: () => h(UiButton, { size: 'sm', variant: 'ghost' }, () => 'Refresh'),
})UiToast
h(UiToastViewport)
// basic
showToast({ type: 'success', title: 'Saved' })
// with description
showToast({
type: 'error',
title: 'Export failed',
description: 'Check your connection and try again.',
})
// with action
showToast({
type: 'info',
title: 'Draft deleted',
action: { label: 'Undo', onClick: () => restoreDraft() },
})UiAlert
// minimal — just text
h(UiAlert, { variant: 'warning' }, {
default: () => 'Your API key expires in 3 days.',
})
// with title
h(UiAlert, { variant: 'danger', title: 'Billing issue' }, {
default: () => 'Your payment method failed. Update it to avoid service interruption.',
})
// with action
h(UiAlert, { variant: 'info', title: 'New version available' }, {
default: () => 'Restart to apply the update.',
actions: () => h(UiButton, { size: 'sm', variant: 'secondary' }, () => 'Restart now'),
})Available variants: info (default), success, warning, danger.
Unlike UiToast, UiAlert is inline and persistent — it renders in the document flow and stays until the consumer removes it.
UiBadge
h(UiBadge, { variant: 'success' }, () => 'Active')
h(UiBadge, { variant: 'warning' }, () => 'Pending')
h(UiBadge, { variant: 'danger' }, () => 'Expired')
h(UiBadge, { variant: 'neutral' }, () => 'Archived')
h(UiBadge, { variant: 'info' }, () => 'Admin')
h(UiBadge, { variant: 'neutral', size: 'sm' }, () => 'Draft')Available variants: neutral (default), success, warning, danger, info.
Available sizes: md (default), sm.
Dropdown With Icons
Menu items accept an optional icon component:
import MyIcon from './icons/MyIcon.vue'
h(UiDropdown, {
items: [
{ label: 'Open', value: 'open', icon: MyIcon },
{ label: 'Duplicate', value: 'duplicate' },
{ type: 'separator' },
{ label: 'Archive', value: 'archive' },
],
onSelect: (item) => console.log(item.value),
}, {
trigger: () => h(UiButton, { variant: 'secondary' }, () => 'Actions'),
})The same icon field works for UiContextMenu items.
Package Exports
Primary entry points:
@sejtax/lui@sejtax/lui/style.css@sejtax/lui/components@sejtax/lui/composables@sejtax/lui/app
Build And Development
Useful scripts:
npm run dev- playground/dev servernpm run build- library build intodistnpm run build:demo- demo buildnpm run typecheck- TypeScript validation
Current Boundaries
Deliberately not included yet:
UiSelect- combobox/searchable select
- date/time pickers
- tables/datagrids
- form framework
- validation system
- notification framework
Project References
Additional project docs:
ARCHITECTURE.mdCOMPONENTS.mdSTYLES.md
