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

ngx-st-tri-state-checkbox

v19.0.0

Published

- [Overview](#overview) - [Installation](#installation) - [Basic Usage](#basic-usage) - [Inputs](#inputs) - [Integration with Forms](#integration-with-forms) - [Usage Examples](#usage-examples) - [Best Practices](#best-practices)

Downloads

268

Readme

Tri-State Checkbox Component - Complete Documentation

Table of Contents


Overview

The ngx-st-tri-state-checkbox component is a checkbox with three states (null/indeterminate, true, false) that cycles through values on click. Features include:

  • Three-state checkbox (indeterminate, checked, unchecked)
  • Customizable state sequence
  • Full Angular Forms integration (ControlValueAccessor)
  • Material Design styling
  • Disabled state support

Installation

npm install ngx-st-tri-state-checkbox

Import the module:

import { NgxStTriStateCheckboxModule } from 'ngx-st-tri-state-checkbox';

@NgModule({
  imports: [NgxStTriStateCheckboxModule]
})
export class AppModule { }

Basic Usage

With ngModel

<ngx-st-tri-state-checkbox
  [(ngModel)]="value">
</ngx-st-tri-state-checkbox>

<p>Current value: {{ value }}</p>

With Reactive Forms

// Component
form = this.fb.group({
  status: [null]
});
<form [formGroup]="form">
  <ngx-st-tri-state-checkbox
    formControlName="status">
  </ngx-st-tri-state-checkbox>
</form>

<p>Current value: {{ form.get('status')?.value }}</p>

Inputs

tape

  • Type: any[]
  • Default: [null, true, false]
  • Description: Array of values that the checkbox cycles through on each click. The component will loop through these values in order.
  • Example:
    <!-- Default: null -> true -> false -> null -->
    [tape]="[null, true, false]"
      
    <!-- Custom: 0 -> 1 -> 2 -> 0 -->
    [tape]="[0, 1, 2]"
      
    <!-- String values: 'unknown' -> 'yes' -> 'no' -->
    [tape]="['unknown', 'yes', 'no']"
      
    <!-- Two states only: false -> true -> false -->
    [tape]="[false, true]"

Integration with Forms

The component implements ControlValueAccessor, making it compatible with Angular Forms (both template-driven and reactive).

Template-Driven Forms

// Component
export class MyComponent {
  myValue: boolean | null = null;
}
<ngx-st-tri-state-checkbox
  [(ngModel)]="myValue"
  name="tristate">
</ngx-st-tri-state-checkbox>

Reactive Forms

// Component
export class MyComponent {
  form = this.fb.group({
    approved: [null],
    enabled: [false],
    status: ['pending']
  });
}
<form [formGroup]="form">
  <ngx-st-tri-state-checkbox
    formControlName="approved">
  </ngx-st-tri-state-checkbox>
  
  <ngx-st-tri-state-checkbox
    formControlName="enabled"
    [tape]="[false, true]">
  </ngx-st-tri-state-checkbox>
  
  <ngx-st-tri-state-checkbox
    formControlName="status"
    [tape]="['pending', 'approved', 'rejected']">
  </ngx-st-tri-state-checkbox>
</form>

Disabled State

// Component
form = this.fb.group({
  status: [{ value: null, disabled: true }]
});

// Or programmatically
this.form.get('status')?.disable();
<ngx-st-tri-state-checkbox
  formControlName="status">
</ngx-st-tri-state-checkbox>

Usage Examples

Example 1: Basic Tri-State Checkbox

// Component
@Component({
  selector: 'app-basic-tristate',
  template: `
    <h3>Approval Status</h3>
    <ngx-st-tri-state-checkbox
      [(ngModel)]="approvalStatus">
    </ngx-st-tri-state-checkbox>
    
    <p>Status: {{ getStatusLabel() }}</p>
  `
})
export class BasicTristateComponent {
  approvalStatus: boolean | null = null;
  
  getStatusLabel(): string {
    if (this.approvalStatus === null) return 'Pending';
    if (this.approvalStatus === true) return 'Approved';
    return 'Rejected';
  }
}

Example 2: Custom State Sequence

// Component
@Component({
  selector: 'app-custom-states',
  template: `
    <h3>Priority Level</h3>
    <ngx-st-tri-state-checkbox
      [(ngModel)]="priority"
      [tape]="priorityLevels">
    </ngx-st-tri-state-checkbox>
    
    <p>Priority: {{ getPriorityLabel() }}</p>
  `
})
export class CustomStatesComponent {
  priority: string = 'low';
  priorityLevels = ['low', 'medium', 'high'];
  
  getPriorityLabel(): string {
    return this.priority.toUpperCase();
  }
}

Example 3: Filter with Tri-State

// Component
@Component({
  selector: 'app-filter',
  template: `
    <div class="filter-section">
      <label>Show Active Users:</label>
      <ngx-st-tri-state-checkbox
        [(ngModel)]="activeFilter"
        [tape]="[null, true, false]">
      </ngx-st-tri-state-checkbox>
      <span>{{ getFilterLabel() }}</span>
    </div>
    
    <div class="users-list">
      <div *ngFor="let user of filteredUsers">
        {{ user.name }} - {{ user.active ? 'Active' : 'Inactive' }}
      </div>
    </div>
  `
})
export class FilterComponent {
  activeFilter: boolean | null = null;
  
  users = [
    { name: 'John', active: true },
    { name: 'Jane', active: false },
    { name: 'Bob', active: true }
  ];
  
  get filteredUsers() {
    if (this.activeFilter === null) {
      return this.users;  // Show all
    }
    return this.users.filter(u => u.active === this.activeFilter);
  }
  
  getFilterLabel(): string {
    if (this.activeFilter === null) return 'All users';
    if (this.activeFilter === true) return 'Active only';
    return 'Inactive only';
  }
}

Example 4: Form with Multiple Tri-State Checkboxes

// Component
@Component({
  selector: 'app-permission-form',
  template: `
    <form [formGroup]="permissionsForm">
      <div class="permission-item">
        <label>Read Access:</label>
        <ngx-st-tri-state-checkbox
          formControlName="read"
          [tape]="accessLevels">
        </ngx-st-tri-state-checkbox>
        <span>{{ getAccessLabel(permissionsForm.get('read')?.value) }}</span>
      </div>
      
      <div class="permission-item">
        <label>Write Access:</label>
        <ngx-st-tri-state-checkbox
          formControlName="write"
          [tape]="accessLevels">
        </ngx-st-tri-state-checkbox>
        <span>{{ getAccessLabel(permissionsForm.get('write')?.value) }}</span>
      </div>
      
      <div class="permission-item">
        <label>Delete Access:</label>
        <ngx-st-tri-state-checkbox
          formControlName="delete"
          [tape]="accessLevels">
        </ngx-st-tri-state-checkbox>
        <span>{{ getAccessLabel(permissionsForm.get('delete')?.value) }}</span>
      </div>
      
      <button (click)="save()">Save Permissions</button>
    </form>
  `
})
export class PermissionFormComponent {
  accessLevels = ['denied', 'inherited', 'allowed'];
  
  permissionsForm = this.fb.group({
    read: ['inherited'],
    write: ['inherited'],
    delete: ['denied']
  });
  
  constructor(private fb: FormBuilder) {}
  
  getAccessLabel(value: string): string {
    const labels = {
      'denied': '❌ Denied',
      'inherited': '↗️ Inherited',
      'allowed': '✅ Allowed'
    };
    return labels[value] || value;
  }
  
  save(): void {
    console.log('Permissions:', this.permissionsForm.value);
  }
}

Example 5: Boolean-Only Toggle (Two States)

// Component
@Component({
  selector: 'app-toggle',
  template: `
    <div class="toggle-switch">
      <label>Enable Feature:</label>
      <ngx-st-tri-state-checkbox
        [(ngModel)]="featureEnabled"
        [tape]="[false, true]">
      </ngx-st-tri-state-checkbox>
      <span>{{ featureEnabled ? 'Enabled' : 'Disabled' }}</span>
    </div>
  `
})
export class ToggleComponent {
  featureEnabled = false;
}

Example 6: Table Column Filter

// Component
@Component({
  selector: 'app-table-filter',
  template: `
    <table>
      <thead>
        <tr>
          <th>
            Name
            <ngx-st-tri-state-checkbox
              [(ngModel)]="sortName"
              [tape]="[null, 'asc', 'desc']">
            </ngx-st-tri-state-checkbox>
          </th>
          <th>
            Active
            <ngx-st-tri-state-checkbox
              [(ngModel)]="filterActive">
            </ngx-st-tri-state-checkbox>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let item of sortedAndFilteredData">
          <td>{{ item.name }}</td>
          <td>{{ item.active }}</td>
        </tr>
      </tbody>
    </table>
  `
})
export class TableFilterComponent {
  sortName: 'asc' | 'desc' | null = null;
  filterActive: boolean | null = null;
  
  data = [
    { name: 'Alice', active: true },
    { name: 'Bob', active: false },
    { name: 'Charlie', active: true }
  ];
  
  get sortedAndFilteredData() {
    let result = [...this.data];
    
    // Filter
    if (this.filterActive !== null) {
      result = result.filter(item => item.active === this.filterActive);
    }
    
    // Sort
    if (this.sortName === 'asc') {
      result.sort((a, b) => a.name.localeCompare(b.name));
    } else if (this.sortName === 'desc') {
      result.sort((a, b) => b.name.localeCompare(a.name));
    }
    
    return result;
  }
}

Example 7: Settings with Default/Enabled/Disabled

// Component
@Component({
  selector: 'app-settings',
  template: `
    <h3>Notification Settings</h3>
    <div class="setting-row" *ngFor="let setting of settings">
      <label>{{ setting.label }}:</label>
      <ngx-st-tri-state-checkbox
        [(ngModel)]="setting.value"
        [tape]="['default', 'enabled', 'disabled']">
      </ngx-st-tri-state-checkbox>
      <span class="setting-value">{{ getSettingDisplay(setting.value) }}</span>
    </div>
    
    <button (click)="saveSettings()">Save</button>
  `
})
export class SettingsComponent {
  settings = [
    { label: 'Email Notifications', value: 'default' },
    { label: 'Push Notifications', value: 'default' },
    { label: 'SMS Notifications', value: 'disabled' }
  ];
  
  getSettingDisplay(value: string): string {
    const display = {
      'default': '⚙️ Use Default',
      'enabled': '✅ Enabled',
      'disabled': '❌ Disabled'
    };
    return display[value] || value;
  }
  
  saveSettings(): void {
    console.log('Saving settings:', this.settings);
  }
}

Best Practices

  1. Use meaningful state sequences that match your use case:

    <!-- For filters: null (all), true (yes), false (no) -->
    [tape]="[null, true, false]"
       
    <!-- For sorting: null (none), 'asc', 'desc' -->
    [tape]="[null, 'asc', 'desc']"
       
    <!-- For permissions: 'denied', 'inherited', 'allowed' -->
    [tape]="['denied', 'inherited', 'allowed']"
  2. Provide visual feedback showing the current state:

    <ngx-st-tri-state-checkbox [(ngModel)]="value"></ngx-st-tri-state-checkbox>
    <span>{{ getStateLabel(value) }}</span>
  3. Use with reactive forms for better control and validation:

    form = this.fb.group({
      status: [null, Validators.required]
    });
  4. Initialize with appropriate default values:

    // For filters, often start with null (show all)
    filterValue: boolean | null = null;
       
    // For toggles, often start with false
    enabled = false;
       
    // For status, start with pending/default
    status = 'pending';
  5. Document the state sequence for other developers:

    // States: null (pending) -> true (approved) -> false (rejected)
    [tape]="[null, true, false]"

State Cycling Behavior

The checkbox cycles through states in the order defined by the tape input:

// Default: [null, true, false]
Click 1: null  -> true
Click 2: true  -> false
Click 3: false -> null (loops back)
Click 4: null  -> true (continues...)

// Custom: ['off', 'on', 'auto']
Click 1: 'off'  -> 'on'
Click 2: 'on'   -> 'auto'
Click 3: 'auto' -> 'off' (loops back)

Visual States

The component uses Material Design checkbox which shows:

  • Indeterminate (dash/minus): Usually for null or first state
  • Checked: Usually for true or second state
  • Unchecked: Usually for false or third state

For custom tape values, the visual representation follows the same pattern based on position in the array.


Common Use Cases

  1. Filters: null (all), true (include), false (exclude)
  2. Sorting: null (none), 'asc', 'desc'
  3. Permissions: 'denied', 'inherited', 'allowed'
  4. Status: 'pending', 'approved', 'rejected'
  5. Toggle: false, true (two-state)
  6. Priority: 'low', 'medium', 'high'

This documentation covers all inputs, behavior, and usage patterns for the Tri-State Checkbox component.