ng-luna
v0.5.5
Published
An Angular component library inspired by Windows XP aesthetics, built with IBM Plex fonts
Downloads
526
Maintainers
Readme
ng-luna
An Angular component library inspired by Windows XP aesthetics, built with IBM Plex fonts.
Overview
ng-luna provides a collection of Angular components styled to match the classic Windows XP design language. The library uses custom styling inspired by Windows XP and IBM Plex fonts for typography.
All components are standalone and implement Angular's reactive forms API where applicable, making them compatible with FormControl, FormGroup, and ngModel. Components are built on Angular CDK for enhanced accessibility, keyboard navigation, drag-and-drop, and cross-platform support.
Installation
npm install ng-lunaPeer Dependencies
This library requires the following peer dependencies:
@angular/common: 19.x.x@angular/core: 19.x.x@angular/forms: 19.x.x
Usage
Import Components
Import the components you need in your Angular application. All components are standalone:
import { ButtonComponent } from 'ng-luna';
@Component({
imports: [ ButtonComponent ],
// ...
})Using Bundled Fonts
The IBM Plex fonts are bundled with ng-luna and need to be copied to your application's assets folder.
Setup
Add the following to your angular.json in the assets array of your project's build configuration:
{
"projects": {
"your-app-name": {
"architect": {
"build": {
"options": {
"assets": [
{
"glob": "**/*",
"input": "node_modules/ng-luna/assets/fonts",
"output": "/assets/fonts"
}
]
}
}
}
}
}
}This copies the font files from the ng-luna package to your application's /assets/fonts/ directory during build.
Using Fonts in Your Styles
Once configured, you can use the fonts directly in your CSS/SCSS:
.my-custom-class {
font-family: 'IBM Plex Sans', sans-serif;
}
.code-snippet {
font-family: 'IBM Plex Mono', monospace;
}
.heading {
font-family: 'IBM Plex Serif', serif;
}Available font families:
'IBM Plex Sans', sans-serif- Default sans-serif font used by components'IBM Plex Mono', monospace- Monospace font'IBM Plex Serif', serif- Serif font
Note: The ng-luna components will automatically use these fonts once they are available in your assets folder.
Using Icons
ng-luna includes Lucide Icons, a comprehensive set of over 1,400 clean, consistent SVG icons that complement the Windows XP aesthetic. Icons are provided as tree-shakable ES modules - only the icons you import will be included in your bundle.
Import Icons
import { IconComponent, Home, Save, Folder, Settings } from 'ng-luna';
@Component({
imports: [ IconComponent ],
template: `
<luna-icon [svg]="homeIcon" size="24"></luna-icon>
<luna-icon [svg]="saveIcon" size="16"></luna-icon>
`
})
export class MyComponent {
homeIcon = Home;
saveIcon = Save;
}Icon Component
Selector: luna-icon
Inputs
svg: string(required) - The SVG string from an imported Lucide iconsize?: IconSize- Icon size:'12' | '16' | '20' | '24' | '32' | '48'(default:'24')
Example
<luna-icon [svg]="homeIcon" size="24"></luna-icon>
<luna-icon [svg]="saveIcon" size="16"></luna-icon>
<luna-button>
<luna-icon [svg]="folderIcon" size="16"></luna-icon>
Open Folder
</luna-button>Available Icons
Lucide provides over 1,400 icons. Browse all available icons at lucide.dev/icons.
Common icons for XP-style interfaces:
Home,Folder,File- NavigationSettings,Tool,Wrench- ConfigurationUser,Users- User managementSave,Download,Upload- File operationsX,Minimize2,Maximize2- Window controlsChevronLeft,ChevronRight,ChevronDown- Navigation arrowsCheck,AlertTriangle- Status indicators
Tree-Shaking
Icons use ES modules for automatic tree-shaking. Only imported icons are included in your final bundle:
// ✅ Good: Only Home and Save icons included in bundle
import { Home, Save } from 'ng-luna';
// ❌ Avoid: Imports entire icon library
import * as Icons from 'ng-luna';Components
All components extend the LunaControl base class, which provides the following common inputs available on every component:
id?: string- Element IDname?: string- Name attributedisabled: boolean- Whether the control is disabled (default:false)tabindex?: number- Tab index for keyboard navigationautofocus: boolean- Whether the control should be autofocused (default:false)
These inputs are inherited from the base class and available on all components unless otherwise noted in the component-specific documentation below.
Button Component
The luna-button component provides a Windows XP-styled button.
Selector: luna-button
Inputs
command?: string- Command to invoke when the button is clickedcommandfor?: string- Element ID that the command is forform?: string- Form element ID to associate withformaction?: string- URL to submit the form to (for submit buttons)formenctype?: FormEnctype- Form encoding type:'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain'formmethod?: FormMethod- HTTP method for form submission:'get' | 'post'formnovalidate: boolean- Whether to bypass form validation (default:false)formtarget?: FormTarget- Where to display form response:'_self' | '_blank' | '_parent' | '_top'popovertarget?: string- ID of popover element to controlpopovertargetaction?: PopoverTargetAction- Popover action:'show' | 'hide' | 'toggle'type: ButtonType- Button type:'button' | 'submit' | 'reset'(default:'button')value?: string- Value attribute for the button
Outputs
click: EventEmitter<MouseEvent>- Emitted when the button is clicked
Example
<luna-button
type="submit"
[disabled]="isLoading"
(click)="onSubmit()">
Submit Form
</luna-button>Checkbox Component
The luna-checkbox component provides a Windows XP-styled checkbox that implements ControlValueAccessor for reactive forms support.
Selector: luna-checkbox
Inputs
label?: string- Label text for the checkboxvalue?: string- Value attribute for the checkbox
Outputs
change: EventEmitter<boolean>- Emitted when the checkbox state changes
Example
<luna-checkbox
[(ngModel)]="isChecked"
label="Accept terms and conditions"
(change)="onCheckboxChange($event)">
</luna-checkbox>Fieldset Component
The luna-fieldset component provides a Windows XP-styled fieldset for grouping form controls.
Selector: luna-fieldset
Inputs
legend?: string- Legend text for the fieldset
Example
<luna-fieldset legend="User Information">
<!-- Form controls here -->
</luna-fieldset>Icon Component
The luna-icon component provides a convenient way to render Lucide icons with automatic sanitization and sizing.
Selector: luna-icon
Note: This component does not inherit from LunaControl and does not have the common base inputs.
Inputs
svg: string(required) - The SVG string from an imported Lucide iconsize?: IconSize- Icon size:'12' | '16' | '20' | '24' | '32' | '48'(default:'24')
Example
<luna-icon [svg]="homeIcon" size="24"></luna-icon>
<luna-button>
<luna-icon [svg]="saveIcon" size="16"></luna-icon>
Save File
</luna-button>import { IconComponent, Home, Save } from 'ng-luna';
@Component({
imports: [ IconComponent ],
// ...
})
export class MyComponent {
homeIcon = Home;
saveIcon = Save;
}For more information on importing and using icons, see the Using Icons section above.
Menu Component
The luna-menu component provides a classic dropdown menu (DOS/Windows 9x style) with a trigger and a list of items. It supports checked items, hover highlighting, arrow-key navigation with cursor highlighting, closing with Escape, and selecting the highlighted option with Enter.
Selector: luna-menu
Trigger directive: lunaMenuTrigger – attach to the element that opens the menu (e.g. a button).
Include the menu theme in your global styles so the overlay and backdrop render correctly:
@use 'ng-luna/theme/menu' as *;Inputs
items: LunaMenuEntry[]– Menu entries. Each item:{ label: string, checked?: boolean, disabled?: boolean }. Use{ separator: true }for a divider line.
Outputs
itemSelect: EventEmitter<LunaMenuItem | null>– Emitted when an item is chosen (ornullwhen the menu is closed without selection, e.g. via Escape or backdrop click).
Example
<luna-menu [items]="menuItems" (itemSelect)="onMenuSelect($event)">
<button lunaMenuTrigger>File</button>
</luna-menu>import { LunaMenuComponent, LunaMenuTriggerDirective } from 'ng-luna';
import type { LunaMenuEntry, LunaMenuItem } from 'ng-luna';
@Component({
imports: [ LunaMenuComponent, LunaMenuTriggerDirective ],
// ...
})
export class MyComponent {
menuItems: LunaMenuEntry[] = [
{ label: 'New' },
{ label: 'Open...' },
{ separator: true },
{ label: 'Save' },
{ label: 'Save As...', checked: true },
{ separator: true },
{ label: 'Exit', disabled: true }
];
onMenuSelect(item: LunaMenuItem | null): void {
if (item) {
console.log('Selected', item.label);
}
}
}Input Component
The luna-input component provides a Windows XP-styled text input that implements ControlValueAccessor for reactive forms support.
Selector: luna-input
Inputs
placeholder?: string- Placeholder texttype: InputType- Input type:'text' | 'password' | 'email'(default:'text')readonly: boolean- Whether the input is readonly (default:false)
Outputs
change: EventEmitter<string>- Emitted when the input value changesblur: EventEmitter<FocusEvent>- Emitted when the input loses focus
Example
<luna-input
[(ngModel)]="username"
type="text"
placeholder="Enter username"
(change)="onInputChange($event)">
</luna-input>Progress Component
The luna-progress component provides a Windows XP-styled progress bar.
Selector: luna-progress
Inputs
value?: number- Current progress valuemax: number- Maximum value (default:100)
Example
<luna-progress
[value]="progressValue"
[max]="100">
</luna-progress>Radio Component
The luna-radio component provides a Windows XP-styled radio button that implements ControlValueAccessor for reactive forms support.
Selector: luna-radio
Inputs
label?: string- Label text for the radio buttonvalue?: string- Value attribute for the radio button
Note: The name attribute is required for grouping radio buttons together.
Outputs
change: EventEmitter<string>- Emitted when the radio button is selected
Example
<luna-radio
name="option"
value="option1"
label="Option 1"
[(ngModel)]="selectedOption">
</luna-radio>
<luna-radio
name="option"
value="option2"
label="Option 2"
[(ngModel)]="selectedOption">
</luna-radio>Select Component
The luna-select component provides a Windows XP-styled select dropdown that implements ControlValueAccessor for reactive forms support.
Selector: luna-select
Inputs
No additional inputs beyond the common base inputs.
Outputs
change: EventEmitter<string>- Emitted when the selection changes
Example
<luna-select
[(ngModel)]="selectedValue"
(change)="onSelectChange($event)">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</luna-select>Slider Component
The luna-slider component provides a Windows XP-styled range slider that implements ControlValueAccessor for reactive forms support.
Selector: luna-slider
Inputs
min: number- Minimum value (default:0)max: number- Maximum value (default:100)step: number- Step value (default:1)vertical: boolean- Whether the slider is vertical (default:false)boxIndicator: boolean- Whether to show a box indicator (default:false)
Outputs
change: EventEmitter<number>- Emitted when the slider value changes
Example
<luna-slider
[(ngModel)]="sliderValue"
[min]="0"
[max]="100"
[step]="1"
(change)="onSliderChange($event)">
</luna-slider>Tabs Component
The luna-tabs component provides a Windows XP-styled tab interface with keyboard navigation support.
Selector: luna-tabs
Inputs
tabs: Tab[]- Array of tab objects withid,label, and optionalcontentactiveTabId?: string- ID of the currently active tab
Outputs
tabChange: EventEmitter<string>- Emitted when a tab is selected
Keyboard Navigation
- Arrow Left/Right - Navigate between tabs
- Tab - Move focus to tab panel content
Example
<luna-tabs
[tabs]="tabs"
[activeTabId]="activeTabId"
(tabChange)="onTabChange($event)">
</luna-tabs>tabs: Tab[] = [
{ id: 'tab1', label: 'Tab 1', content: 'Content 1' },
{ id: 'tab2', label: 'Tab 2', content: 'Content 2' }
];Textarea Component
The luna-textarea component provides a Windows XP-styled textarea that implements ControlValueAccessor for reactive forms support.
Selector: luna-textarea
Inputs
placeholder?: string- Placeholder textrows?: number- Number of visible rowscols?: number- Number of visible columnsreadonly: boolean- Whether the textarea is readonly (default:false)
Outputs
change: EventEmitter<string>- Emitted when the textarea value changesblur: EventEmitter<FocusEvent>- Emitted when the textarea loses focus
Example
<luna-textarea
[(ngModel)]="message"
[rows]="5"
[cols]="40"
placeholder="Enter your message"
(change)="onTextareaChange($event)">
</luna-textarea>Window Component
The luna-window component provides a Windows XP-styled draggable window with title bar and controls.
Selector: luna-window
Inputs
title?: string- Window title textshowMinimize: boolean- Whether to show the minimize button (default:true)showMaximize: boolean- Whether to show the maximize button (default:true)showHelp: boolean- Whether to show the help button (default:false)showClose: boolean- Whether to show the close button (default:true)isMaximized: boolean- Whether the window is currently maximized (default:false)boundaryElement?: string- CSS selector for element that constrains window draggingscrollable: boolean- Whether the window body should be scrollable (default:false)
Outputs
minimize: EventEmitter<void>- Emitted when the minimize button is clickedmaximize: EventEmitter<void>- Emitted when the maximize button is clickedrestore: EventEmitter<void>- Emitted when the restore button is clickedhelp: EventEmitter<void>- Emitted when the help button is clickedclose: EventEmitter<void>- Emitted when the close button is clicked
Dragging
The window can be dragged by its title bar. Dragging is automatically disabled when the window is maximized.
Example
<luna-window
title="My Application"
[showHelp]="true"
boundaryElement=".container"
[scrollable]="true"
(minimize)="onMinimize()"
(maximize)="onMaximize()"
(close)="onClose()">
<div class="window-body">
Window content goes here
</div>
</luna-window>Accessibility & CDK Features
All components leverage Angular CDK for enhanced functionality:
- Focus Management - All form components track focus states for better accessibility
- Keyboard Navigation - Tabs support arrow key navigation with focus management
- Screen Reader Support - Components announce state changes via
LiveAnnouncer - Drag & Drop - Window component supports draggable functionality with boundary constraints
- Scrolling - Window body supports scrollable content areas
- Platform Detection - Components adapt behavior based on platform/browser
- RTL Support - Input and button components support right-to-left layouts
- Type Coercion - Number inputs use type-safe coercion utilities
Screen reader announcements are non-intrusive and only audible to assistive technology users.
Reactive Forms Support
Components that implement ControlValueAccessor (checkbox, input, radio, select, slider, textarea) can be used with Angular's reactive forms:
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
export class MyComponent
{
form: FormGroup;
constructor(private fb: FormBuilder)
{
this.form = this.fb.group({
username: [''],
email: [''],
agreeToTerms: [false]
});
}
}<form [formGroup]="form">
<luna-input
formControlName="username"
placeholder="Username">
</luna-input>
<luna-input
formControlName="email"
type="email"
placeholder="Email">
</luna-input>
<luna-checkbox
formControlName="agreeToTerms"
label="I agree to the terms">
</luna-checkbox>
</form>Development
For a detailed explanation of the library architecture, build process, and how the public API works, see ARCHITECTURE.md.
Building the Library
npm run buildClean build (use when the build fails, e.g. "Cannot find module 'rxjs'"):
- Clean – Remove
node_modules,dist, and.angularif present. Optionally removepackage-lock.jsonfor a full dependency refresh. - Install –
npm install - Build –
npm run build
The library uses import { Observable } from 'rxjs' like other Angular libraries. rxjs is in peerDependencies (for apps that use the library) and in devDependencies (for this repo's build). If the build still cannot find rxjs, confirm that node_modules/rxjs exists after npm install; if not, run npm install rxjs --save-dev and try again.
The build process uses ng-packagr to:
- Compile TypeScript to ESM modules
- Generate type definitions
- Bundle everything into
dist/fesm2022/ng-luna.mjs - Copy assets (fonts, SCSS themes)
- Create a production-ready
package.json
All exports go through src/public-api.ts → src/controls/index.ts → individual components.
Project Structure
ng-luna/
├── src/
│ ├── controls/ # Component library controls
│ │ ├── button/ # Button component
│ │ ├── checkbox/ # Checkbox component
│ │ ├── fieldset/ # Fieldset component
│ │ ├── input/ # Input component
│ │ ├── progress/ # Progress component
│ │ ├── radio/ # Radio component
│ │ ├── select/ # Select component
│ │ ├── slider/ # Slider component
│ │ ├── tabs/ # Tabs component
│ │ ├── textarea/ # Textarea component
│ │ ├── window/ # Window component
│ │ └── index.ts # Controls barrel export
│ ├── theme/ # Theme files (fonts, styles)
│ │ ├── _fonts.scss # IBM Plex font imports
│ │ ├── _palette.scss # Color palette
│ │ ├── _graphics.scss # SVG graphics
│ │ └── _global.scss # Global styles
│ └── public-api.ts # Public API surface
├── ng-package.json # ng-packagr configuration
├── package.json # Package dependencies
└── tsconfig.json # TypeScript configurationDependencies
- @angular/cdk (19.x.x) - Angular Component Dev Kit
- @ibm/plex (v6.4.1) - IBM Plex font families (fonts are bundled with the library)
- lucide-static - Lucide icon set (icons are bundled with the library)
Publishing
Prerequisites
- Create an npm account at npmjs.com if you don't have one
- Login to npm from the command line:
npm loginPublishing Steps
- Build the library:
npm run build- Test the build (optional but recommended):
cd dist
npm packThis creates a .tgz file you can inspect or test locally before publishing.
- Publish to npm:
npm publish ./distPublishing Updates
When you need to publish a new version:
# Update the version number (choose one):
npm version patch # 0.0.1 -> 0.0.2 (bug fixes)
npm version minor # 0.0.1 -> 0.1.0 (new features)
npm version major # 0.0.1 -> 1.0.0 (breaking changes)
# Build and publish
npm run build
npm publish ./distLicense
MIT License - see LICENSE file for details.
Copyright (c) 2025 Adam R Moss
