diff --git a/libs/angular/src/directives/autofocus.directive.ts b/libs/angular/src/directives/autofocus.directive.ts
deleted file mode 100644
index c11b473f99..0000000000
--- a/libs/angular/src/directives/autofocus.directive.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Directive, ElementRef, Input, NgZone } from "@angular/core";
-import { take } from "rxjs/operators";
-
-import { Utils } from "@bitwarden/common/platform/misc/utils";
-
-@Directive({
- selector: "[appAutofocus]",
-})
-export class AutofocusDirective {
- @Input() set appAutofocus(condition: boolean | string) {
- this.autofocus = condition === "" || condition === true;
- }
-
- private autofocus: boolean;
-
- constructor(
- private el: ElementRef,
- private ngZone: NgZone,
- ) {}
-
- ngOnInit() {
- if (!Utils.isMobileBrowser && this.autofocus) {
- if (this.ngZone.isStable) {
- this.el.nativeElement.focus();
- } else {
- this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
- }
- }
- }
-}
diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts
index b585591aa6..1177106767 100644
--- a/libs/angular/src/jslib.module.ts
+++ b/libs/angular/src/jslib.module.ts
@@ -2,12 +2,13 @@ import { CommonModule, DatePipe } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { AutofocusDirective } from "@bitwarden/components";
+
import { CalloutComponent } from "./components/callout.component";
import { BitwardenToastModule } from "./components/toastr.component";
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
import { A11yTitleDirective } from "./directives/a11y-title.directive";
import { ApiActionDirective } from "./directives/api-action.directive";
-import { AutofocusDirective } from "./directives/autofocus.directive";
import { BoxRowDirective } from "./directives/box-row.directive";
import { CopyClickDirective } from "./directives/copy-click.directive";
import { CopyTextDirective } from "./directives/copy-text.directive";
diff --git a/libs/components/src/input/autofocus.directive.ts b/libs/components/src/input/autofocus.directive.ts
new file mode 100644
index 0000000000..f8161ee6e0
--- /dev/null
+++ b/libs/components/src/input/autofocus.directive.ts
@@ -0,0 +1,54 @@
+import { Directive, ElementRef, Input, NgZone, Optional } from "@angular/core";
+import { take } from "rxjs/operators";
+
+import { Utils } from "@bitwarden/common/platform/misc/utils";
+
+/**
+ * Interface for implementing focusable components. Used by the AutofocusDirective.
+ */
+export abstract class FocusableElement {
+ focus: () => void;
+}
+
+/**
+ * Directive to focus an element.
+ *
+ * @remarks
+ *
+ * If the component provides the `FocusableElement` interface, the `focus`
+ * method will be called. Otherwise, the native element will be focused.
+ */
+@Directive({
+ selector: "[appAutofocus], [bitAutofocus]",
+})
+export class AutofocusDirective {
+ @Input() set appAutofocus(condition: boolean | string) {
+ this.autofocus = condition === "" || condition === true;
+ }
+
+ private autofocus: boolean;
+
+ constructor(
+ private el: ElementRef,
+ private ngZone: NgZone,
+ @Optional() private focusableElement: FocusableElement,
+ ) {}
+
+ ngOnInit() {
+ if (!Utils.isMobileBrowser && this.autofocus) {
+ if (this.ngZone.isStable) {
+ this.focus();
+ } else {
+ this.ngZone.onStable.pipe(take(1)).subscribe(this.focus.bind(this));
+ }
+ }
+ }
+
+ private focus() {
+ if (this.focusableElement) {
+ this.focusableElement.focus();
+ } else {
+ this.el.nativeElement.focus();
+ }
+ }
+}
diff --git a/libs/components/src/input/index.ts b/libs/components/src/input/index.ts
index 4036b8ab94..9713d2b919 100644
--- a/libs/components/src/input/index.ts
+++ b/libs/components/src/input/index.ts
@@ -1 +1,2 @@
export * from "./input.module";
+export * from "./autofocus.directive";
diff --git a/libs/components/src/search/search.component.html b/libs/components/src/search/search.component.html
index 2396dc3423..82bd153e10 100644
--- a/libs/components/src/search/search.component.html
+++ b/libs/components/src/search/search.component.html
@@ -8,6 +8,7 @@
void;
private notifyOnTouch: () => void;
+ @ViewChild("input") private input: ElementRef;
+
protected id = `search-id-${nextId++}`;
protected searchText: string;
@Input() disabled: boolean;
@Input() placeholder: string;
+ focus() {
+ this.input.nativeElement.focus();
+ }
+
onChange(searchText: string) {
if (this.notifyOnChange != undefined) {
this.notifyOnChange(searchText);