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

@makigamestudio/ui-ionic

v0.9.1

Published

Ionic implementation of @makigamestudio/ui-core. Provides Ionic-specific button components with signal-based state management.

Downloads

529

Readme

@makigamestudio/ui-ionic

Ionic 8 component implementations for the ui-core library. This package provides ready-to-use Angular 21 standalone components built with Ionic Framework.

Features

  • Zoneless Change Detection — No zone.js dependency
  • Signal-Based Architecture — Angular Signals for all reactivity
  • Standalone Components — No NgModules required
  • Ionic 8 Integration — Native Ionic components and styling
  • TypeScript Strict Mode — Full type safety

Installation

npm install @makigamestudio/ui-ionic @makigamestudio/ui-core

Peer Dependencies

{
  "@angular/core": "^21.0.0",
  "@ionic/angular": "^8.0.0",
  "@makigamestudio/ui-core": "^0.5.0"
}

Theming

Quick Start

Import the theme file in your global styles to get both CSS variables and component styles:

// src/global.scss or src/styles.scss
@use '@makigamestudio/ui-ionic/theme.scss';

// Your custom overrides AFTER the import
:root {
  --maki-spacing-md: 16px; // Override default spacing
}

Note: Importing theme.scss automatically includes global-styles.scss, so you only need one import.

Theming Strategy

This library follows a hybrid approach for maximum flexibility:

  1. Uses Ionic's color system — All components use --ion-color-* variables (primary, secondary, success, etc.) so they automatically match your app's Ionic theme.

  2. Defines custom design tokens — Only for properties that Ionic doesn't provide: spacing, border radius, shadows, and typography.

This means when you change your Ionic theme colors, all ui-ionic components automatically adapt. No duplicate configuration needed!

Import Order

Always import the theme before your custom overrides:

// ✅ CORRECT ORDER
@use '@makigamestudio/ui-ionic/theme.scss';

:root {
  --maki-spacing-lg: 20px;
}

// ❌ WRONG ORDER (overrides won't work)
:root {
  --maki-spacing-lg: 20px;
}
@use '@makigamestudio/ui-ionic/theme.scss'; // This resets your overrides!

Available CSS Variables

All variables include fallback values for robustness.

Spacing Scale

--maki-spacing-xs: 4px;
--maki-spacing-sm: 8px;
--maki-spacing-md: 12px;
--maki-spacing-lg: 16px;
--maki-spacing-xl: 24px;
--maki-spacing-xxl: 32px;

Border Radius Scale

--maki-radius-xs: 4px;
--maki-radius-sm: 8px;
--maki-radius-md: 12px;
--maki-radius-lg: 16px;
--maki-radius-xl: 24px;
--maki-radius-full: 9999px;

Shadows

--maki-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
/* Dark mode: 0 2px 8px rgba(0, 0, 0, 0.3) */

Typography - Font Sizes

--maki-font-size-xs: 0.75rem;
--maki-font-size-sm: 0.875rem;
--maki-font-size-md: 1rem;
--maki-font-size-lg: 1.125rem;
--maki-font-size-xl: 1.25rem;
--maki-font-size-xxl: 1.5rem;
--maki-font-size-xxxl: 2rem;

Typography - Line Heights

--maki-line-height-tight: 1.25;
--maki-line-height-normal: 1.5;
--maki-line-height-relaxed: 1.75;

Typography - Letter Spacing

--maki-letter-spacing-normal: 0;
--maki-letter-spacing-wide: 0.02em;

**Note:** Tooltip show/hide delays (250ms/200ms) are intentionally hardcoded in the service to maintain consistent UX and prevent consumers from setting extreme values that would break expected behavior.


**Z-Index Hierarchy:**

```
Modal (10000)         ← Highest layer (blocks everything)
├─ Tooltip (9999)     ← Above popovers, below modals
├─ Popover (9000)     ← Dropdowns and context menus
├─ Sticky (1020)      ← Sticky headers
└─ Dropdown (1000)    ← Basic dropdowns
```

This hierarchy ensures tooltips appear above popovers/dropdowns but below modal backdrops.

### Customization Examples

#### Override Spacing

```scss
@use '@makigamestudio/ui-ionic/theme.scss';

:root {
  --maki-spacing-md: 16px; // Change medium spacing
  --maki-spacing-lg: 24px; // Change large spacing
}
```

#### Change Shadows

```scss
@use '@makigamestudio/ui-ionic/theme.scss';

:root {
  --maki-box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); // Stronger shadow
}
```

#### Customize Border Radius

```scss
@use '@makigamestudio/ui-ionic/theme.scss';

:root {
  --maki-radius-sm: 12px; // Rounder tooltips and cards
}
```

### Shadow DOM and RTL Support

The library properly handles Ionic's Shadow DOM components and provides RTL (Right-to-Left) language support through CSS custom properties.

#### Styling Ionic Shadow DOM Components

Ionic components like `ion-button` use Shadow DOM, which prevents regular CSS from penetrating component boundaries. The library uses Ionic's exposed CSS custom properties (e.g., `--padding-start`, `--padding-end`) to style these components:

```typescript
// button.component.ts
ion-button {
  --padding-start: var(--maki-spacing-sm, 0.5rem); // Maps to Shadow DOM
  --padding-end: var(--maki-spacing-sm, 0.5rem);
}
```

**Why this pattern is correct:**

1. **Shadow DOM Encapsulation**: Regular `padding` CSS won't work on `ion-button` — you must use Ionic's custom properties
2. **Consistent Theming**: All spacing uses `--maki-*` tokens, ensuring layout themes affect Ionic components
3. **RTL Support**: `--padding-start`/`--padding-end` automatically flip in RTL languages (unlike `padding-left`/`padding-right`)

**RTL Behavior:**

```
LTR (English):  --padding-start → left,  --padding-end → right
RTL (Arabic):   --padding-start → right, --padding-end → left
```

### Theming Architecture

The library follows a **hybrid theming approach** that cleanly separates UI-agnostic logic from CSS-specific implementation:

#### Separation of Concerns

**ui-core (UI-Agnostic):**

- ✅ Pure computational logic (position calculations, visibility rules)
- ✅ State management with signals
- ✅ Behavioral timing (show/close delays remain hardcoded: 250ms/200ms)
- ❌ No CSS, no DOM manipulation, no framework-specific code

**ui-ionic (Ionic-Specific):**

- ✅ CSS variables and theming
- ✅ DOM manipulation and rendering
- ✅ Ionic component integration
- ✅ CSS transitions and animations

#### Timing Strategy

The library uses a **hybrid timing approach**:

**Behavioral Delays (Service-Controlled):**

```typescript
// ui-core/tooltip.service.ts - Hardcoded for consistent UX
getShowDelayMs(): number { return 250; }  // Wait before showing tooltip
getCloseDelayMs(): number { return 200; } // Grace period before hiding
```

These values are intentionally hardcoded to prevent consumers from setting extreme values (e.g., 5000ms) that would break expected tooltip behavior.

**CSS Timing (Theme-Controlled):**

```scss
// ui-ionic/theme.scss - Themable for visual preferences
```

These values can be customized per layout theme (compact uses faster transitions, comfortable uses slower).

**Why This Separation:**

- **Behavioral delays** define UX patterns (how long to wait before showing)
- **CSS timing** defines visual polish (how fast animations feel)
- Mixing these concerns would make layout themes accidentally break tooltip interaction patterns

### Dark Mode Toggle

The library is **theme-agnostic** and works with both light and dark modes. Theme customization and dark mode implementation is the **consumer's responsibility**. The playground app demonstrates a complete dark theme implementation pattern.

#### 1. Create a Theme Toggle Service

Create a signal-based service to manage theme state:

```typescript
// services/theme-toggle.service.ts
import { effect, inject, Injectable, Renderer2, RendererFactory2, signal } from '@angular/core';
import { DOCUMENT } from '@angular/common';

export type ThemeMode = 'light' | 'dark';

@Injectable({ providedIn: 'root' })
export class ThemeToggleService {
  private readonly document = inject(DOCUMENT);
  private readonly renderer: Renderer2;
  private readonly storageKey = 'theme-mode';

  private readonly _colorThemeMode = signal<ThemeMode>(this.getInitialColorTheme());
  readonly mode = this._colorThemeMode.asReadonly();

  constructor() {
    const rendererFactory = inject(RendererFactory2);
    this.renderer = rendererFactory.createRenderer(null, null);

    // Apply initial theme class
    this.applyThemeClass(this._colorThemeMode());

    // Batch localStorage writes using effect
    effect(() => {
      const currentMode = this._colorThemeMode();
      try {
        localStorage.setItem(this.storageKey, currentMode);
      } catch (error) {
        console.error('Failed to persist theme:', error);
      }
    });
  }

  toggleColorTheme(): void {
    this.setColorTheme(this._colorThemeMode() === 'light' ? 'dark' : 'light');
  }

  setColorTheme(mode: ThemeMode): void {
    this._colorThemeMode.set(mode);
    this.applyThemeClass(mode);
  }

  private getInitialColorTheme(): ThemeMode {
    try {
      const stored = localStorage.getItem(this.storageKey);
      if (stored === 'light' || stored === 'dark') return stored;
    } catch {}

    // Fallback to prefers-color-scheme
    if (this.document.defaultView?.matchMedia?.('(prefers-color-scheme: dark)').matches) {
      return 'dark';
    }
    return 'light';
  }

  private applyThemeClass(mode: ThemeMode): void {
    const body = this.document.body;
    if (!body) return;

    if (mode === 'dark') {
      this.renderer.addClass(body, 'dark');
    } else {
      this.renderer.removeClass(body, 'dark');
    }
  }
}
```

#### 2. Define Dark Theme Variables

Create a dark theme stylesheet with Ionic's recommended dark palette:

```scss
// theme-dark.scss
body.dark {
  // Ionic Core Colors (Dark Mode)
  --ion-background-color: #121212;
  --ion-text-color: #ffffff;

  // Primary - Lighter for better contrast on dark backgrounds
  --ion-color-primary: #428cff;
  --ion-color-primary-contrast: #ffffff;

  // Secondary
  --ion-color-secondary: #50c8ff;
  --ion-color-secondary-contrast: #ffffff;

  // Success
  --ion-color-success: #2fdf75;
  --ion-color-success-contrast: #000000;

  // Warning
  --ion-color-warning: #ffd534;
  --ion-color-warning-contrast: #000000;

  // Danger
  --ion-color-danger: #ff4961;
  --ion-color-danger-contrast: #ffffff;

  // Light - Inverted (becomes darker)
  --ion-color-light: #222428;
  --ion-color-light-contrast: #ffffff;

  // Medium
  --ion-color-medium: #989aa2;
  --ion-color-medium-contrast: #000000;

  // Dark - Inverted (becomes lighter)
  --ion-color-dark: #f4f5f8;
  --ion-color-dark-contrast: #000000;

  // Maki Design Tokens
  --maki-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
```

#### 3. Import Dark Theme

Add the dark theme import to your global styles:

```scss
// styles.scss
@use '@makigamestudio/ui-ionic/theme.scss';
@use './theme-dark.scss'; // Your dark theme overrides
```

#### 4. Add Theme Toggle UI

Use the theme service in your components:

```typescript
import { computed, inject } from '@angular/core';
import { IonToggle } from '@ionic/angular/standalone';
import { ThemeToggleService } from './services/theme-toggle.service';

@Component({
  imports: [IonToggle],
  template: `
    <ion-toggle
      [checked]="isDark()"
      (ionChange)="onThemeToggle($event)"
      aria-label="Toggle dark mode"
    />
  `
})
export class MyComponent {
  readonly themeService = inject(ThemeToggleService);
  readonly isDark = computed(() => this.themeService.mode() === 'dark');

  onThemeToggle(event: CustomEvent): void {
    this.themeService.setColorTheme(event.detail.checked ? 'dark' : 'light');
  }
}
```

#### Key Points

- **CSS Variables Required**: Always use CSS variables in your components (e.g., `var(--ion-color-primary)`) so they adapt automatically to theme changes
- **Class-Based Theming**: The service applies/removes the `dark` class on `<body>` to trigger theme overrides
- **Persistence**: Theme preference is saved to `localStorage` and restored on app load
- **Fallback Support**: Automatically detects `prefers-color-scheme: dark` if no saved preference exists
- **Signal-Based**: Fully reactive using Angular signals for zoneless compatibility

For a complete working example, see the [playground app](../../projects/playground/src/app/home/home.page.ts) source code.

### Layout Theme Toggle

In addition to color themes (light/dark), you can implement layout themes to adjust spacing, typography for different user preferences. The playground demonstrates three layout modes: **compact**, **default**, and **comfortable**.

#### Layout Theme Comparison

| Property           | Compact | Default (Baseline) | Comfortable |
| ------------------ | ------- | ------------------ | ----------- |
| **Line Height**    |         |                    |             |
| Tight              | 1.2     | 1.25               | 1.4         |
| Normal             | 1.3     | 1.5                | 1.75        |
| Relaxed            | 1.5     | 1.75               | 2.0         |
| **Letter Spacing** |         |                    |             |
| Normal             | 0       | 0                  | 0           |
| Wide               | 0.02em  | 0.02em             | 0.04em      |


#### Layout Theme Options

- **Compact**: Reduced spacing (15-20% smaller), tighter line heights — ideal for information-dense interfaces or large screens
- **Default**: Standard spacing and typography (library baseline) — balanced for most use cases
- **Comfortable**: Increased spacing (20-25% larger), relaxed line heights, wider letter spacing — better accessibility and readability

#### 1. Create Layout Theme Stylesheets

Create separate SCSS files for each layout variant:

```scss
// theme-layout-compact.scss
body.layout-compact {
  // Reduced spacing (20% smaller)
  --maki-spacing-xxs: 0.1rem;
  --maki-spacing-xs: 0.2rem;
  --maki-spacing-sm: 0.4rem;
  --maki-spacing-md: 0.6rem;
  --maki-spacing-lg: 0.8rem;
  --maki-spacing-xl: 1.2rem;
  --maki-spacing-xxl: 1.6rem;

  // Tighter typography
  --maki-font-size-xs: 0.6875rem;
  --maki-font-size-sm: 0.8125rem;
  --maki-font-size-md: 0.9375rem;
  // ... other font sizes

  // Smaller border radius
  --maki-radius-xs: 0.2rem;
  --maki-radius-sm: 0.375rem;
  // ... other radius values
}

// theme-layout-comfortable.scss
body.layout-comfortable {
  // Increased spacing (25% larger)
  --maki-spacing-xxs: 0.15625rem;
  --maki-spacing-xs: 0.3125rem;
  --maki-spacing-sm: 0.625rem;
  --maki-spacing-md: 0.9375rem;
  --maki-spacing-lg: 1.25rem;
  --maki-spacing-xl: 1.875rem;
  --maki-spacing-xxl: 2.5rem;

  // Larger typography for readability
  --maki-font-size-xs: 0.8125rem;
  --maki-font-size-sm: 0.9375rem;
  --maki-font-size-md: 1.0625rem;
  // ... other font sizes

  // Larger border radius
  --maki-radius-xs: 0.3125rem;
  --maki-radius-sm: 0.625rem;
  // ... other radius values
}
```

#### 2. Update Theme Service

Extend your `ThemeToggleService` to manage layout modes:

```typescript
export type LayoutMode = 'compact' | 'default' | 'comfortable';

@Injectable({ providedIn: 'root' })
export class ThemeToggleService {
  private readonly layoutStorageKey = 'layout-mode';
  private readonly _layoutThemeMode = signal<LayoutMode>(this.getInitialLayoutTheme());
  readonly layoutThemeMode = this._layoutThemeMode.asReadonly();

  constructor() {
    // ... existing color theme setup

    // Apply initial layout class
    this.applyLayoutClass(this._layoutThemeMode());

    // Batch layout localStorage writes
    effect(() => {
      const currentLayoutMode = this._layoutThemeMode();
      try {
        localStorage.setItem(this.layoutStorageKey, currentLayoutMode);
      } catch (error) {
        console.error('Failed to persist layout theme:', error);
      }
    });
  }

  setLayoutTheme(mode: LayoutMode): void {
    this._layoutThemeMode.set(mode);
    this.applyLayoutClass(mode);
  }

  private getInitialLayoutTheme(): LayoutMode {
    try {
      const stored = localStorage.getItem(this.layoutStorageKey);
      if (stored === 'compact' || stored === 'default' || stored === 'comfortable') {
        return stored;
      }
    } catch {}
    return 'default';
  }

  private applyLayoutClass(mode: LayoutMode): void {
    const body = this.document.body;
    if (!body) return;

    // Remove all layout classes
    this.renderer.removeClass(body, 'layout-compact');
    this.renderer.removeClass(body, 'layout-comfortable');

    // Apply appropriate class (default has no class)
    if (mode === 'compact') {
      this.renderer.addClass(body, 'layout-compact');
    } else if (mode === 'comfortable') {
      this.renderer.addClass(body, 'layout-comfortable');
    }
  }
}
```

#### 3. Import Layout Themes

Add layout theme imports to your global styles:

```scss
// styles.scss
@use '@makigamestudio/ui-ionic/theme.scss';
@use './theme-dark.scss';
@use './theme-layout-compact.scss';
@use './theme-layout-comfortable.scss';
```

#### 4. Add Layout Switcher UI

Use `ion-segment` for layout mode selection:

```typescript
import { computed, inject } from '@angular/core';
import { IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';

@Component({
  imports: [IonSegment, IonSegmentButton, IonLabel],
  template: `
    <ion-segment [value]="layoutThemeMode()" (ionChange)="onLayoutChange($event)">
      <ion-segment-button value="compact">
        <ion-label>Compact</ion-label>
      </ion-segment-button>
      <ion-segment-button value="default">
        <ion-label>Default</ion-label>
      </ion-segment-button>
      <ion-segment-button value="comfortable">
        <ion-label>Comfortable</ion-label>
      </ion-segment-button>
    </ion-segment>
  `
})
export class MyComponent {
  readonly themeService = inject(ThemeToggleService);
  readonly layoutThemeMode = computed(() => this.themeService.layoutThemeMode());

  onLayoutChange(event: CustomEvent): void {
    const layout = event.detail.value as LayoutMode;
    this.themeService.setLayoutTheme(layout);
  }
}
```

#### Key Points

- **Independent Themes**: Color and layout themes work independently — combine light/dark with any layout mode
- **Signal-Based**: Use `computed()` to derive component state from service signals for zoneless compatibility
- **Default Has No Class**: The `default` layout uses the base theme variables (no class applied)
- **Consistent Variable Names**: Layout themes only override `--maki-*` spacing, typography, and radius variables
- **Accessibility Friendly**: `comfortable` mode improves readability for users who need larger text

For complete examples including both color and layout themes, see the [playground app](../../projects/playground/src/app/home/home.page.ts) source code.

## Components

### ButtonComponent

Ionic button with loading state, icon support, and dropdown functionality.

```typescript
import { ButtonComponent } from '@makigamestudio/ui-ionic';

// In your component
button: IonicActionButton = {
  type: ActionButtonType.Button,
  label: 'Save',
  icon: 'save-outline',
  color: 'primary',
  fill: 'solid'
};
```

```html
<maki-button [button]="button" />
```

### ActionButtonListComponent

Popover list of action buttons.

```typescript
import { ActionButtonListComponent } from '@makigamestudio/ui-ionic';

buttons: IonicActionButton[] = [
  { type: ActionButtonType.Button, label: 'Edit', icon: 'create-outline' },
  { type: ActionButtonType.Button, label: 'Delete', icon: 'trash-outline', color: 'danger' }
];
```

```html
<maki-action-button-list [buttons]="buttons" />
```

### MakiTooltipDirective

Device-aware tooltips with Ionic color support.

```html
<button makiTooltip="Click to save" makiTooltipColor="primary">Save</button>
```

## Development

### Building the Library

```bash
npm run build:ionic
```

### Testing

```bash
npm run test:ionic
```

### Watch Mode

```bash
npm run watch:ionic
```

## License

MIT

## More Information

For detailed API documentation, examples, and architectural guidelines, see the [GitHub repository](https://github.com/gdor/ui-core).