| |

Angular 72 🅰️ Custom Directives in Depth

In the previous chapters, you encountered directives repeatedly — *ngIf, *ngFor, [ngClass], [ngStyle], [ngModel]. They are the tools that shape the DOM, attach behavior to elements, and make templates dynamic. But until now, every directive you used was one Angular shipped. This chapter is about writing your own.

Custom directives are the extension mechanism of Angular templates. When you find yourself repeating the same DOM manipulation — highlighting on hover, auto-focusing inputs, detecting clicks outside a dropdown, lazy-loading images — the correct answer is almost never “copy that logic into every component.” It is a directive. A directive encapsulates the behavior once, applies it everywhere with an attribute, and keeps component templates clean of imperative DOM code.

This chapter covers the two categories of custom directives: attribute directives (which change appearance or behavior of an element) and structural directives (which change the DOM structure itself). It also covers the directive composition API, the modern way to attach directives to a component’s host element without inheritance. The chapter assumes you have read the previous chapter on HostBinding and HostListener; directives are where those tools live.

Key point: A component is a directive with a template. An attribute directive changes how an existing element looks or behaves. A structural directive changes the DOM by adding, removing, or manipulating elements. Every directive is a class with a @Directive() decorator that contains a selector. The selector is what tells Angular which elements in a template should have the directive applied .


Why custom directives exist

Before writing one, it is worth understanding the problem they solve and why the solution is not a component.

The repetition problem. A hover highlight is three lines of logic: listen for mouseenter, set a style, listen for mouseleave, clear it. If you have six places in your application that need this behavior, a component is the wrong abstraction. A component renders something; a highlight directive attaches behavior to something that already exists. Repeating the logic six times is a maintenance trap. A directive writes it once and applies it with one attribute .

The encapsulation problem. DOM manipulation in a component’s TypeScript is invisible in the template. A reader looking at <p class="promo"> does not know that some constructor code is changing its background color on hover. An attribute directive makes the behavior visible: <p appHighlight class="promo"> announces that this element has hover highlighting .

The structural problem. Some behaviors need to change the DOM itself — conditionally show a block, repeat a template for each item in a list, render one of several possible layouts. Attribute directives cannot do this. They receive an element that already exists and modify it. Structural directives receive a template and decide whether and how many times to instantiate it .

The composition problem. A component may need behaviors that are conceptually separate: a menu that can be resized, dragged, and tooltipped. Building all of this into the component mixes concerns and bloats the class. The directive composition API lets a component declare hostDirectives: [Resizable, Draggable, Tooltip] and receive all three behaviors on its host element without inheritance and without the consumer knowing the directive selectors .

The trade-off. Directives add indirection. A directive’s behavior is not visible in the template that uses it — only its selector. Overusing directives for things that should be components makes templates cryptic. The rule of thumb: if it renders markup, it is a component. If it modifies existing markup, it is a directive.


a. Attribute Directives: Changing Appearance and Behavior

An attribute directive is a class with @Directive() and a selector that matches an attribute. Angular instantiates the directive for every element in the template that matches the selector. The directive receives a reference to that element through ElementRef and can modify it.

The canonical example is a highlight directive. The CLI generates the file:

ng generate directive highlight

The generated class has the selector [appHighlight] and an empty body. The first thing to add is ElementRef, which gives access to the host DOM element through its nativeElement property .

import { Directive, ElementRef } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appHighlight]',
})
export class HighlightDirective {
  constructor(private el: ElementRef) {
    this.el.nativeElement.style.backgroundColor = 'yellow';
  }
}

Applying <p appHighlight>Highlight me!</p> creates an instance of the directive, injects the <p> element, and sets its background to yellow. The element does not need to know anything; the directive finds it by selector.

Modifying nativeElement.style directly works, but Angular recommends Renderer2 for DOM manipulation. Renderer2 abstracts the platform, so the code works in server-side rendering and in environments where the DOM is not directly accessible . For simple style changes in a browser-only application, ElementRef is acceptable. For anything more complex, Renderer2 is the correct tool.

The directive becomes useful when it accepts configuration. An @Input() aliased to the directive’s selector lets the consumer pass a value using the same attribute that applies the directive:

@Input() appHighlight = 'yellow';

Now <p [appHighlight]="'lightblue'"> applies the directive and sets the highlight color in one binding . The selector and the input share the same name, which is why the directive is usable with a value without a separate attribute.

Events complete the picture. @HostListener binds a method to an event on the host element:

@HostListener('mouseenter') onMouseEnter() {
  this.highlight(this.appHighlight);
}

@HostListener('mouseleave') onMouseLeave() {
  this.highlight('');
}

The directive now has state (appHighlight), behavior (highlight), and lifecycle (the event listeners). It is a self-contained piece of DOM logic that any element can use .


b. Structural Directives: Changing the DOM

A structural directive changes the DOM by adding, removing, or repeating elements. It does this by receiving a TemplateRef (the content it controls) and a ViewContainerRef (the location where it can render that content). The directive decides when to call viewContainer.createEmbeddedView(templateRef) and when to call viewContainer.clear() .

The asterisk syntax is the key to understanding structural directives. When you write *appUnless="condition", Angular transforms it into an <ng-template> that hosts the directive and wraps the element and its descendants:

<p *appUnless="condition">Content</p>

becomes:

<ng-template [appUnless]="condition">
  <p>Content</p>
</ng-template>

The ng-template is the actual element the directive is applied to. The <p> is the template content that the directive may or may not render .

A custom *appUnless directive (the opposite of *ngIf) injects TemplateRef and ViewContainerRef, and uses a setter input to react to condition changes:

@Directive({
  standalone: true,
  selector: '[appUnless]',
})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}

When the condition is falsy and no view exists, the directive creates the embedded view. When the condition is truthy and a view exists, it clears the container. The hasView flag prevents redundant work on every change detection cycle .

A structural directive can also provide a template context — data that the template can reference through let variables. The *ngFor directive provides index, first, last, even, and odd in addition to the item itself. A custom directive does this by passing an object as the second argument to createEmbeddedView:

this.viewContainer.createEmbeddedView(this.templateRef, {
  $implicit: item,
  index: i,
  first: i === 0,
  last: i === collection.length - 1,
});

The $implicit property is what let item binds to when no explicit key is given. The other properties are bound with let i="index" and similar syntax .

One limitation: only one structural directive per element can use the shorthand syntax. Because the asterisk creates a single <ng-template>, applying two structural directives to the same element is ambiguous — which one wraps the other? Use <ng-container> as a wrapper when you need both, or use the long-form <ng-template> syntax explicitly .


c. Directive Composition and the hostDirectives API

The directive composition API lets a component apply directives to its own host element without the consumer knowing the directive selectors. It is defined with the hostDirectives property in the @Component or @Directive decorator .

@Component({
  selector: 'admin-menu',
  template: 'admin-menu.html',
  hostDirectives: [MenuBehavior],
})
export class AdminMenu {}

When Angular renders <admin-menu>, it also creates an instance of MenuBehavior and applies its host bindings to the <admin-menu> element. The directive’s selector is ignored — MenuBehavior does not need a selector that matches admin-menu .

By default, the inputs and outputs of host directives are not exposed on the component. A consumer of <admin-menu> cannot bind to menuId or listen for menuClosed unless the component explicitly includes them:

@Component({
  hostDirectives: [{
    directive: MenuBehavior,
    inputs: ['menuId'],
    outputs: ['menuClosed'],
  }],
})
export class AdminMenu {}

The inputs and outputs can also be aliased to avoid name collisions:

inputs: ['menuId: id'],
outputs: ['menuClosed: closed'],

Now the consumer writes <admin-menu id="top-menu" (closed)="logMenuClosed()">. The directive’s internals are hidden behind the component’s public API .

The execution order of host directives matters. Host directives always execute their constructor, lifecycle hooks, and bindings before the component or directive they are applied to. This means a component can override any host binding set by a host directive, and a deeper chain of host directives resolves outermost-first .

A practical caveat: inputs defined on a host directive are not automatically available in the component’s template. If you write hostDirectives: [{ directive: SomeDirective, inputs: ['someId'] }], the someId input is exposed to the component’s consumers, but the component’s own template cannot read it through this.someId. You need a separate @Input() on the component if the template needs the value .

Host directives must be standalone: true. They cannot be declared in an NgModule .


Complete Example Session

This session builds a hover-highlight attribute directive, a custom structural directive that mimics *ngIf, and a composed component using hostDirectives.

// ============================================
// PART 1: THE ATTRIBUTE DIRECTIVE
// ============================================

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appHighlight]',
})
export class HighlightDirective {
  @Input() appHighlight = 'yellow';
  @Input() defaultColor = '';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.appHighlight);
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(this.defaultColor);
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}

// Usage:
// <p [appHighlight]="'lightblue'" [defaultColor]="'transparent'">
//   Hover me
// </p>

// ============================================
// PART 2: THE CUSTOM STRUCTURAL DIRECTIVE
// ============================================

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appUnless]',
})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}

// Usage:
// <p *appUnless="isHidden">Shown when isHidden is false</p>
// <p *appUnless="!isHidden">Shown when isHidden is true</p>

// ============================================
// PART 3: THE STRUCTURAL DIRECTIVE WITH CONTEXT
// ============================================

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appRepeat]',
})
export class RepeatDirective {
  constructor(
    private templateRef: TemplateRef<unknown>,
    private viewContainer: ViewContainerRef
  ) {}

  @Input() set appRepeat(collection: unknown[]) {
    this.viewContainer.clear();
    collection.forEach((item, index) => {
      this.viewContainer.createEmbeddedView(this.templateRef, {
        $implicit: item,
        index,
        first: index === 0,
        last: index === collection.length - 1,
      });
    });
  }
}

// Usage:
// <ul>
//   <li *appRepeat="let item of items; index as i; first as isFirst">
//     {{ i }}: {{ item }} {{ isFirst ? '(first)' : '' }}
//   </li>
// </ul>

// ============================================
// PART 4: THE HOST DIRECTIVE
// ============================================

import { Directive, HostListener, input } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appClickOutside]',
})
export class ClickOutsideDirective {
  enabled = input(true);

  @HostListener('document:click', ['$event'])
  onClick(event: MouseEvent) {
    // In a real directive, check whether the click was
    // inside the host element and emit an output if not.
  }
}

// ============================================
// PART 5: THE COMPOSED COMPONENT
// ============================================

import { Component } from '@angular/core';

@Component({
  selector: 'app-dropdown',
  standalone: true,
  template: `
    <button (click)="open = !open">Toggle</button>
    @if (open) {
      <div class="menu"><ng-content /></div>
    }
  `,
  hostDirectives: [
    {
      directive: ClickOutsideDirective,
      inputs: ['enabled'],
    },
  ],
})
export class DropdownComponent {
  open = false;
}

// Usage:
// <app-dropdown [enabled]="true">
//   <a href="/profile">Profile</a>
// </app-dropdown>

// ============================================
// PART 6: THE DIRECTIVE WITH RENDERER2
// ============================================

import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appBorder]',
})
export class BorderDirective {
  @Input() appBorder = '2px solid black';

  constructor(
    private el: ElementRef,
    private renderer: Renderer2
  ) {}

  @HostListener('mouseenter') onEnter() {
    this.renderer.setStyle(this.el.nativeElement, 'border', this.appBorder);
  }

  @HostListener('mouseleave') onLeave() {
    this.renderer.removeStyle(this.el.nativeElement, 'border');
  }
}

// ============================================
// PART 7: THE AUTO-FOCUS DIRECTIVE
// ============================================

import { Directive, ElementRef, AfterViewInit } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appAutoFocus]',
})
export class AutoFocusDirective implements AfterViewInit {
  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    this.el.nativeElement.focus();
  }
}

// Usage: <input appAutoFocus />

// ============================================
// PART 8: THE LAZY-LOAD DIRECTIVE
// ============================================

import { Directive, ElementRef, Input, OnDestroy } from '@angular/core';

@Directive({
  standalone: true,
  selector: 'img[appLazyLoad]',
})
export class LazyLoadDirective implements OnDestroy {
  @Input() appLazyLoad = '';
  private observer?: IntersectionObserver;

  constructor(private el: ElementRef<HTMLImageElement>) {
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          this.el.nativeElement.src = this.appLazyLoad;
          this.observer?.disconnect();
        }
      });
    });
    this.observer.observe(this.el.nativeElement);
  }

  ngOnDestroy() {
    this.observer?.disconnect();
  }
}

// Usage: <img [appLazyLoad]="imageUrl" alt="Photo" />

// ============================================
// PART 9: THE COPY-TO-CLIPBOARD DIRECTIVE
// ============================================

import { Directive, HostListener, input } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[appCopyClipboard]',
})
export class CopyClipboardDirective {
  textToCopy = input.required<string>({ alias: 'appCopyClipboard' });

  @HostListener('click')
  async copy() {
    try {
      await navigator.clipboard.writeText(this.textToCopy());
    } catch (err) {
      console.error('Copy failed', err);
    }
  }
}

// Usage: <button [appCopyClipboard]="'Hello'">Copy</button>

// ============================================
// PART 10: THE STANDALONE DIRECTIVE IMPORT
// ============================================

import { Component } from '@angular/core';
import { HighlightDirective } from './highlight.directive';
import { UnlessDirective } from './unless.directive';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [HighlightDirective, UnlessDirective],
  template: `
    <p [appHighlight]="'lightblue'">Hover me</p>
    <p *appUnless="false">Shown</p>
  `,
})
export class DemoComponent {}

The ten parts cover the attribute directive, the custom structural directive, the structural directive with context, a host directive, the composed component, Renderer2 usage, auto-focus, lazy-load, copy-to-clipboard, and standalone imports.


Quick Reference

The Two Types of Custom Directives

TypePurposeSelector ExampleCore Dependencies
AttributeChange appearance/behavior[appHighlight]ElementRef, HostListener
StructuralChange DOM structure[appUnless]TemplateRef, ViewContainerRef

The Directive Decorator Options

OptionPurpose
selectorCSS selector that identifies the directive
standaloneWhether the directive is standalone (default: true in modern Angular)
hostHost bindings and listeners (modern replacement for @HostBinding/@HostListener)
hostDirectivesDirectives to apply to the host element
inputs / outputsExplicit API surface for host directives

The Structural Directive Pattern

StepCode
Inject dependenciesconstructor(private tpl: TemplateRef, private vcr: ViewContainerRef)
Show contentthis.vcr.createEmbeddedView(this.tpl)
Hide contentthis.vcr.clear()
Provide contextthis.vcr.createEmbeddedView(this.tpl, { $implicit: item })

The Shorthand Translation

ShorthandLong Form
*appUnless="cond"<ng-template [appUnless]="cond">...</ng-template>
*appRepeat="let x of xs"<ng-template appRepeat let-x [appRepeatOf]="xs">...</ng-template>
*ngFor="let x of xs; index as i"<ng-template ngFor let-x [ngForOf]="xs" let-i="index">

Best Practices

✅ Do This:

// Use standalone directives and import them
@Component({ imports: [HighlightDirective] })              // ✅
// Use Renderer2 for DOM manipulation
this.renderer.setStyle(this.el.nativeElement, 'border', '2px solid'); // ✅
// Use the host property in the directive decorator
@Directive({
  host: { '(mouseenter)': 'onEnter()', '[class.active]': 'isActive' },
})                                                         // ✅
// Expose host directive inputs/outputs explicitly
hostDirectives: [{ directive: MenuBehavior, inputs: ['menuId'] }] // ✅
// Use input() signal for directive configuration
color = input('yellow');                                   // ✅

❌ Don’t Do This:

// Don't use @HostBinding/@HostListener in new code
@HostBinding('style.color') color;                         // ❌ use host: {}
// Don't manipulate DOM directly without Renderer2 in complex cases
this.el.nativeElement.innerHTML = '<p>...</p>';            // ❌ security risk
// Don't use structural directives for simple conditions
// Use @if / @for block syntax instead.
// Don't apply two structural directives to the same element
// <p *ngIf="x" *ngFor="let y of ys"> // ❌ invalid
// Don't forget to import standalone directives
// They must be in the component's imports array.           // ❌

Common Pitfalls

PitfallWhy It HappensFix
Directive not appliedMissing import in componentAdd to imports array
Input not bindingSelector and input name mismatchAlias input to selector name
Structural directive doesn’t toggleMissing hasView guardTrack whether the view exists
Host directive input not in templateInputs exposed to consumer onlyAdd separate @Input() on component
DOM manipulation breaks SSRDirect nativeElement accessUse Renderer2
Multiple structural directives errorOnly one <ng-template> per elementUse <ng-container> wrapper

Real-World Examples

1. Hover Highlight

@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  @HostListener('mouseenter') onEnter() { /* set color */ }
}

2. Custom If (Unless)

@Input() set appUnless(condition: boolean) {
  condition ? this.vcr.clear() : this.vcr.createEmbeddedView(this.tpl);
}

3. Auto-Focus

ngAfterViewInit() { this.el.nativeElement.focus(); }

4. Click Outside

@HostListener('document:click', ['$event'])
onClick(event: MouseEvent) { /* check target */ }

5. Lazy Load Image

new IntersectionObserver((entries) => { /* load when visible */ });

6. Copy to Clipboard

navigator.clipboard.writeText(this.textToCopy());

7. Repeat with Context

collection.forEach((item, i) => {
  this.vcr.createEmbeddedView(this.tpl, { $implicit: item, index: i });
});

8. Host Directive Composition

hostDirectives: [{ directive: TooltipDirective, inputs: ['text'] }]

9. Renderer2 Border

this.renderer.setStyle(this.el.nativeElement, 'border', '2px solid');

10. Standalone Import

@Component({ imports: [HighlightDirective, UnlessDirective] })

Visual: Attribute vs Structural Directive

┌──────────────────────────────────────────────┐
│  ATTRIBUTE DIRECTIVE                         │
│                                              │
│  <p appHighlight>Text</p>                    │
│       │                                      │
│       ▼                                      │
│  Directive modifies <p> in place             │
│    ├─ Changes style                          │
│    ├─ Listens to events                      │
│    └─ Element stays in DOM                   │
│                                              │
├──────────────────────────────────────────────┤
│  STRUCTURAL DIRECTIVE                        │
│                                              │
│  <p *appUnless="cond">Text</p>               │
│       │                                      │
│       ▼                                      │
│  Becomes:                                    │
│  <ng-template [appUnless]="cond">            │
│    <p>Text</p>                               │
│  </ng-template>                              │
│       │                                      │
│       ▼                                      │
│  Directive decides whether to render         │
│    ├─ createEmbeddedView() → add             │
│    └─ clear() → remove                       │
│                                              │
└──────────────────────────────────────────────┘

Visual: The Asterisk Shorthand

┌──────────────────────────────────────────────┐
│  ASTERISK SHORTHAND TRANSLATION              │
│                                              │
│  Shorthand:                                  │
│  <p *appUnless="condition">Content</p>       │
│                                              │
│  Long form:                                  │
│  <ng-template [appUnless]="condition">       │
│    <p>Content</p>                            │
│  </ng-template>                              │
│                                              │
│  The directive applies to <ng-template>.     │
│  The <p> is the template content.            │
│  The directive decides whether to render.    │
│                                              │
│  Only ONE structural directive per element.  │
│  Use <ng-container> for wrapping.            │
│                                              │
└──────────────────────────────────────────────┘

Visual: Structural Directive with Context

┌──────────────────────────────────────────────┐
│  TEMPLATE CONTEXT                            │
│                                              │
│  Directive:                                  │
│  vcr.createEmbeddedView(tpl, {               │
│    $implicit: item,     ← let item           │
│    index: i,            ← let i="index"      │
│    first: i === 0,      ← let f="first"      │
│  });                                         │
│                                              │
│  Template:                                   │
│  <li *appRepeat="let item of items;          │
│                   index as i;                │
│                   first as f">               │
│    {{ i }}: {{ item }}                       │
│  </li>                                       │
│                                              │
└──────────────────────────────────────────────┘

Visual: Directive Composition

┌──────────────────────────────────────────────┐
│  HOST DIRECTIVES                             │
│                                              │
│  @Component({                                │
│    hostDirectives: [                         │
│      MenuBehavior,                           │
│      { directive: Tooltip,                   │
│        inputs: ['text'] },                   │
│    ],                                        │
│  })                                          │
│  class AdminMenu {}                          │
│                                              │
│  <admin-menu text="Settings">                │
│       │                                      │
│       ├─ MenuBehavior applied                │
│       ├─ Tooltip applied                     │
│       └─ Component template rendered         │
│                                              │
│  Consumer sees only <admin-menu>.            │
│  Directive selectors are hidden.             │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
Attribute directiveChanges appearance/behavior in place
Structural directiveChanges DOM structure
Structural dependenciesTemplateRef, ViewContainerRef
Asterisk shorthandCreates <ng-template> wrapper
Template contextObject with $implicit and named keys
One structural directivePer element with shorthand
Composition APIhostDirectives in decorator
Host directive orderRuns before the component
DOM manipulationPrefer Renderer2
StandaloneDefault in modern Angular

Key takeaways:

  • A component is a directive with a template. Attribute directives modify existing elements. Structural directives decide whether and how many times to render a template. The selector determines which elements a directive applies to .
  • Attribute directives use ElementRef and HostListener. They receive the host element, can change its properties, styles, and classes, and can listen for events. Renderer2 is preferred over direct nativeElement access for portability .
  • Structural directives use TemplateRef and ViewContainerRef. The asterisk syntax wraps the element in an <ng-template>, which the directive receives. Calling createEmbeddedView adds the content; calling clear removes it .
  • The template context provides data to the template. $implicit is the default binding for let variables. Named keys are accessed with let x="key". This is how *ngFor exposes index, first, last, and odd .
  • Only one structural directive can use the asterisk shorthand per element. The shorthand creates a single <ng-template>, so two structural directives would be ambiguous. Use <ng-container> as a wrapper when nesting is needed .
  • The directive composition API attaches directives to a component’s host. hostDirectives applies behavior without the consumer knowing the directive selectors. Inputs and outputs must be explicitly exposed to become part of the component’s API .
  • Host directives run before the component. Their constructors, lifecycle hooks, and host bindings execute first, which means the component can override their host bindings if needed. Chains of host directives resolve outermost-first .

Remember: Custom directives are how Angular applications extend the template language. An attribute directive is a reusable piece of DOM behavior — highlight on hover, focus on load, detect outside clicks. A structural directive is a reusable piece of DOM structure — conditionally show, repeat, or switch. The composition API is how components adopt those behaviors without inheritance. Write directives when the behavior is orthogonal to rendering. Write components when the behavior is the rendering. The boundary is clear, and respecting it keeps templates readable and components focused.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!