@lockness/ui

@lockness/ui

VIEW

@lockness/ui provides a collection of pre-built UI components powered by Hono JSX, Tailwind CSS, and Unpoly. Inspired by shadcn/ui, components are copied to your project rather than imported from a package, giving you full ownership and customization capabilities.

Philosophy

Instead of installing components as dependencies, @lockness/ui uses a CLI tool to copy component source code directly into your project. This approach offers:

  • Full Ownership - Components live in your codebase, modify them freely
  • No Black Boxes - See exactly what code you're using
  • Tree Shaking - Only include components you actually use
  • Zero Lock-in - Components work independently of package versions
  • Learning - Study component source code in your project

Installation

There are two ways to use @lockness/ui:

CLI Mode (Recommended)

Copy components directly to your project:

bash
# List available components
deno run -A jsr:@lockness/ui list

# Add a single component
deno run -A jsr:@lockness/ui add button

# Add multiple components
deno run -A jsr:@lockness/ui add button card root-layout

# Force overwrite existing files
deno run -A jsr:@lockness/ui add button --force

# Custom target directory (default: app/view)
deno run -A jsr:@lockness/ui add button --dir src/components

What happens when you add a component:

  1. Component source files are copied to app/view/components/ui/
  2. Internal dependencies (like utils) are automatically installed
  3. Import paths are rewritten to work in your project structure
  4. Instructions shown for adding npm dependencies to deno.json

Library Mode (Quick Testing)

For quick prototyping, import components directly:

bash
deno add @lockness/ui

Or add to deno.json:

json
{
    "imports": {
        "@lockness/ui": "jsr:@lockness/ui@^<version>"
    }
}

Then import:

typescript
import { Button, Card, cn, RootLayout } from '@lockness/ui/components'

Available Components

For detailed documentation of each component, fetch the corresponding endpoint:

Component Documentation Standards

When creating new components or updating existing ones, please follow these documentation standards to ensure consistency across the library:

  1. DOCS.md is the Single Source of Truth

    This file should contain all technical details, API references, and installation instructions.
  2. Use standard Markdown. Avoid using raw HTML (like <div class="...">) for styling content; let the Markdown renderer handle the presentation.
  3. Structure: Title (# ComponentName)
  4. Description
  5. Installation command
  6. Usage example (basic)
  7. Props table
  8. Theming / CSS Variables
  9. Examples are for Demonstration

    examples.tsx should primarily focus on rendering live examples.
  10. It should render the content of DOCS.md first, then display interactive examples.
  11. Include at least 3 distinct, real-world usage scenarios.
  12. Clean Markdown

    Do not try to style the documentation with inline HTML/CSS classes.
  13. Use standard Markdown elements: lists, code blocks, blockquotes, bold/italic text.
  14. Links should be standard Markdown links: [Label](url).

Icons

@lockness/ui includes a comprehensive icon library based on Lucide icons, optimized for Hono JSX with consistent sizing and theming support.

Installation

bash
deno run -A jsr:@lockness/ui add icons

Usage

tsx
import { ArrowRightIcon, CheckCircleIcon, GithubIcon } from '@view/icons.tsx'

// Default size (24px)
<ArrowRightIcon />

// Custom size
<CheckCircleIcon size={16} />

// With custom class
<GithubIcon class="text-muted-foreground" />

// Custom stroke width
<ArrowRightIcon strokeWidth={1.5} />

IconProps Interface

All icons accept the same props:

PropTypeDefaultDescription
sizenumber24Icon width and height (px)
classstring-Additional CSS classes
strokeWidthnumber2SVG stroke width

Available Icons

Navigation & UI

IconDescription
ArrowRightIconRight arrow
ArrowLeftIconLeft arrow
ChevronRightIconChevron right
ChevronDownIconChevron down
MenuIconHamburger menu
XIconClose/X
ExternalLinkIconExternal link
NavigationIconNavigation arrow

Status & Feedback

IconDescription
CheckIconSimple checkmark
CheckCircleIconCheckmark in circle (success)
XCircleIconX in circle (error/destructive)
InfoCircleIconInfo icon in circle
AlertTriangleIconWarning triangle
LoaderIconLoading spinner

Actions

IconDescription
CopyIconCopy to clipboard
SearchIconSearch/magnify
UploadIconUpload
PlayIconPlay button

Development

IconDescription
CodeIconCode brackets
TerminalIconTerminal/CLI
DatabaseIconDatabase
LayersIconLayers/stack
BoxIcon3D box/package
GitBranchIconGit branch

Status Indicators

IconDescription
ZapIconLightning/speed
ShieldIconSecurity shield
ClockIconTime/clock
SparklesIconSparkles/AI

Communication

IconDescription
MailIconEmail
UsersIconGroup/team
UserIconSingle user
MegaphoneIconAnnouncement

Brands

IconDescription
GithubIconGitHub logo
TwitterIconTwitter/X
DiscordIconDiscord

Documents

IconDescription
FileIconFile/document
BookIconBook/docs
ImageIconImage

Theme & Settings

IconDescription
SunIconLight mode
MoonIconDark mode
SettingsIconSettings/cog
PaletteIconColors/palette

Misc

IconDescription
RobotIconRobot/AI
RocketIconRocket/launch
PuzzleIconPuzzle piece
WrenchIconTools/wrench
LayoutGridIconGrid layout
FormInputIconForm input
BarChartIconBar chart

Creating Custom Icons

Create your own icons following the same pattern:

tsx
import type { FC } from '@lockness/hono'

interface IconProps {
    size?: number
    class?: string
    strokeWidth?: number
}

const IconBase: FC<IconProps & { children?: unknown }> = ({
    size = 24,
    class: className,
    strokeWidth = 2,
    children,
}) => (
    <svg
        xmlns='http://www.w3.org/2000/svg'
        width={size}
        height={size}
        viewBox='0 0 24 24'
        fill='none'
        stroke='currentColor'
        stroke-width={strokeWidth}
        stroke-linecap='round'
        stroke-linejoin='round'
        class={className}
    >
        {children}
    </svg>
)

export const MyCustomIcon: FC<IconProps> = (props) => (
    <IconBase {...props}>
        {/* SVG paths here */}
        <circle cx='12' cy='12' r='10' />
    </IconBase>
)

Usage with Components

Icons integrate seamlessly with other UI components:

tsx
import { Button } from '@view/components/ui/Button.tsx'
import { ArrowRightIcon, GithubIcon } from '@view/icons.tsx'

// Button with icon
<Button>
    <GithubIcon size={16} class="mr-2" />
    View on GitHub
</Button>

// Icon button
<Button variant="outline" size="icon">
    <ArrowRightIcon size={16} />
</Button>

Utilities

cn() - Class Name Utility

Merge Tailwind classes with proper conflict resolution.

tsx
import { cn } from '@view/lib/utils.ts'

// Simple merge
cn('px-2 py-1', 'bg-blue-500')
// => 'px-2 py-1 bg-blue-500'

// Conflict resolution (last wins)
cn('px-2', 'px-4')
// => 'px-4'

// Conditional classes
cn('text-base', isLarge && 'text-lg')
cn('text-base', { 'text-lg': isLarge })

Add to deno.json:

json
{
    "imports": {
        "clsx": "npm:clsx@2.1.1",
        "tailwind-merge": "npm:tailwind-merge@2.6.0"
    }
}

Components with Dependencies

Some components depend on other components. The CLI automatically resolves and installs all dependencies:

bash
# Adding pricing installs 6 files:
# - pricing (main component)
# - button (for CTA buttons)
# - card (for card containers)
# - badge (for promotional badges)
# - icons (for check/cross icons)
# - utils (class name utility)
deno run -A jsr:@lockness/ui add pricing

Components and their dependencies:

ComponentDependencies
buttonutils
cardutils
badgeutils
alertutils, icons
pricingutils, icons, button, card, badge
accordionutils
tabsutils

When you run add, the CLI:

  1. Resolves all internal dependencies recursively
  2. Copies each component to the correct location
  3. Rewrites import paths automatically
  4. Shows npm dependencies to add to deno.json

Project Structure

After adding components, your project will look like this:

plaintext
app/view/
├── components/
│   └── ui/
│       ├── Button.tsx       # Added via CLI
│       ├── Card.tsx         # Added via CLI
│       └── RootLayout.tsx   # Added via CLI
└── lib/
    └── utils.ts             # Added automatically (cn utility)

Import paths in copied components are automatically rewritten:

tsx
// Original (in packages/ui/components/Button.tsx)
import { cn } from '../lib/utils.ts'

// After copying to app/view/components/ui/Button.tsx
import { cn } from '../../lib/utils.ts'

CLI Commands Reference

list

Show all available components:

bash
deno run -A jsr:@lockness/ui list

add

Add one or more components to your project:

bash
# Single component
deno run -A jsr:@lockness/ui add button

# Multiple components
deno run -A jsr:@lockness/ui add button card root-layout

# With options
deno run -A jsr:@lockness/ui add button --force
deno run -A jsr:@lockness/ui add button --dir src/components

Options:

  • --force, -f - Overwrite existing files without prompting
  • --dir <path>, -d <path> - Target directory (default: app/view)
  • --help, -h - Show help message

Component Caching

When run remotely from JSR:

  • Components are fetched via HTTPS
  • Cached in ~/.lockness/ui-cache/ for faster subsequent commands
  • Cache is organized by version
  • No network needed after first fetch

Why This Approach?

Benefits over traditional component libraries:

  1. Full Ownership - Modify components without package constraints or breaking changes
  2. No Black Boxes - See exactly what code you're using, no hidden dependencies
  3. Tree Shaking - Only include what you need, no unused code
  4. No Version Lock - Components work independently of package versions
  5. Learning - Study source code in your project to understand how they work
  6. Customization - Change anything without forking or ejecting

Next Steps