Skip to main content

Getting Started with Focusly


1. Register a Focus Target

Add the focusly-target directive to any focusable element. Each target must have a stable focuslyElementId (used for programmatic focus).


<div [focuslyGroupHost]="1">
<input
focusly-target
[focuslyElementId]="'firstName'"
placeholder="First name"
/>

<input
focusly-target
[focuslyElementId]="'lastName'"
placeholder="Last name"
/>

<button
focusly-target
[focuslyElementId]="'saveBtn'"
>
Save
</button>
</div>

The optional focuslyGroupHost directive provides a default group for targets inside the container (targets can also set focuslyGroup directly).


2. Programmatic Focus

Inject the FOCUSLY_SERVICE_API and request focus using setFocusByElementId.


import { Component, inject } from '@angular/core';
import { FOCUSLY_SERVICE_API } from '@zaybu/focusly';

@Component({
standalone: true,
template: `
<button (click)="focusSave()">Focus Save</button>
`
})
export class DemoComponent {
private readonly focuslyService = inject(FOCUSLY_SERVICE_API);

focusSave() {
// If your button is in group 1, you can optionally pass the group:
// this.focuslyService.setFocusByElementId('saveBtn', 1);

this.focuslyService.setFocusByElementId('saveBtn');
}
}


3. Keyboard Navigation

Use the focusly directive when you want keyboard navigation. It extends focusly-target and adds focuslyRow + focuslyColumnso Focusly can move around your layout with defined keys.


<select
focusly
[focuslyGroup]="1"
[focuslyRow]="2"
[focuslyColumn]="3"
>
<option>Buy</option>
<option>Sell</option>
</select>

Tip: If you use focuslyGroupHost you can omit focuslyGroup on each element.


4. Keyboard Shortcuts

Focusly shortcuts are declared with focuslyShortcut, and captured by a container using focuslyShortcutHost.

Shortcut host

Add focuslyShortcutHost to a container that should listen for shortcut key presses (it listens on keydown using capture).


<div [focuslyGroupHost]="1" focuslyShortcutHost>
...
</div>

Group-scoped shortcut (default scope)

The default scope for focuslyShortcut is 'group'. If you don’t provide focuslyGroup on the shortcut, the host/group host group is used.


<div
[focuslyGroupHost]="1"
focuslyShortcutHost
focuslyShortcut
[focuslyKey]="'enter'"
(focuslyAction)="onEnterKey($event)"
>
...
</div>

Global shortcut

Global shortcuts work regardless of the active group.


<div focuslyShortcutHost>
<button
focuslyShortcut
[focuslyKey]="'ctrl+k'"
[focuslyShortcutScope]="'global'"
(focuslyAction)="openCommandPalette($event)"
>
Command Palette
</button>
</div>

Element-scoped shortcut

Element shortcuts require an elementId. You can provide it explicitly via focuslyElementId.


<input
focusly-target
[focuslyGroup]="1"
[focuslyElementId]="'searchBox'"
/>

<button
focuslyShortcut
[focuslyKey]="'escape'"
[focuslyShortcutScope]="'element'"
[focuslyElementId]="'searchBox'"
(focuslyAction)="clearSearch($event)"
>
Clear search
</button>

Prevent shortcuts while typing

Use focuslyPreventInTextActions to prevent a shortcut triggering while the user is typing in an input/textarea/contenteditable.


<div
[focuslyGroupHost]="1"
focuslyShortcutHost
focuslyShortcut
[focuslyKey]="'enter'"
[focuslyPreventInTextActions]="true"
(focuslyAction)="submit($event)"
>
...
</div>

Priority

If multiple shortcuts match the same key chord in the same scope, Focusly uses the highest focuslyPriority.


<button
focuslyShortcut
[focuslyKey]="'enter'"
[focuslyShortcutScope]="'global'"
[focuslyPriority]="10"
(focuslyAction)="doImportantThing($event)"
>
Important action
</button>


Tab Navigation with Shortcuts

Focusly works well with dynamic UI such as tab systems. In this example (using NgZorro nz-tabs), each tab is assigned its own group, and Alt + 1–5 switches tabs and focuses the first field inside the tab.

Template


<nz-tabs
[focuslyGroupHost]="3"
focuslyShortcutHost
[focuslyShortcuts]="focuslyShortcuts"
[nzSelectedIndex]="selectedIndex()"
(nzSelectedIndexChange)="selectedIndex.set($event)"
>
<nz-tab nzTitle="Trading Data">
<app-trading-ticket></app-trading-ticket>
</nz-tab>

<nz-tab nzTitle="Customer Data">
<app-customer></app-customer>
</nz-tab>

<nz-tab nzTitle="Analysis">
<app-market-analysis></app-market-analysis>
</nz-tab>

<nz-tab nzTitle="Risk">
<app-risk-limits></app-risk-limits>
</nz-tab>

<nz-tab nzTitle="Audit">
<app-audit-compliance></app-audit-compliance>
</nz-tab>
</nz-tabs>

Component


import { inject, signal } from '@angular/core';
import { FOCUSLY_SERVICE_API, FocuslyShortcuts } from '@zaybu/focusly';

export class TradingPageComponent {
private readonly focuslyService = inject(FOCUSLY_SERVICE_API);

selectedIndex = signal(0);

focuslyShortcuts: FocuslyShortcuts = {
'alt+1': () => {
this.selectIndex(0);
this.focuslyService.setFocusByElementId('tradingFirstField');
},
'alt+2': () => {
this.selectIndex(1);
this.focuslyService.setFocusByElementId('customerFirstField');
},
'alt+3': () => {
this.selectIndex(2);
this.focuslyService.setFocusByElementId('marketFirstField');
},
'alt+4': () => {
this.selectIndex(3);
this.focuslyService.setFocusByElementId('riskFirstField');
},
'alt+5': () => {
this.selectIndex(4);
this.focuslyService.setFocusByElementId('auditFirstField');
}
};

selectIndex(index: number) {
this.selectedIndex.set(index);
}
}

When a shortcut is triggered:

  • The tab index is changed.
  • The new tab renders its content.
  • Focusly requests focus for the target field.
  • The directive confirms DOM focus before updating application state.

5. Styling the Active Element

Focusly applies .focusly-active to the currently active target.


.focusly-active {
outline: 2px solid blue;
outline-offset: 2px;
}