npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sejtax/lui

v1.0.10

Published

Compact Vue 3 UI library for settings screens, admin panels, overlays, and small app shells.

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:

  • useClickOutside
  • useEscape
  • useControllableState
  • useScrollLock
  • useOverlayPosition
  • useToast
  • useFocusTrap
  • useRovingTabindex

Directional exports at 1.0.4:

  • UiEmptyState
  • app-shell contracts from @sejtax/lui/app

Installation

From npm:

npm install @sejtax/lui

For 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/lui for most projects
  • use file:../lui only 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:

  • light
  • dark

Style entry:

  • @sejtax/lui/style.css

Overlay Components

Overlay family uses the same positioning foundation.

  • UiDropdown: trigger-based menu
  • UiContextMenu: right-click menu from cursor position
  • UiPopover: compact floating content
  • UiTooltip: short non-interactive hint
  • UiDialog: 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:

  • success
  • info
  • warning
  • error

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 server
  • npm run build - library build into dist
  • npm run build:demo - demo build
  • npm 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.md
  • COMPONENTS.md
  • STYLES.md