diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts
index f64d1a010fc9f07ee30ab2eda51004b23b1db7eb..b2a574838f9116b672a021322bfa1b5a009f147c 100644
--- a/alfa-client/libs/design-system/src/index.ts
+++ b/alfa-client/libs/design-system/src/index.ts
@@ -23,6 +23,6 @@ export * from './lib/icons/send-icon/send-icon.component';
 export * from './lib/icons/spinner-icon/spinner-icon.component';
 export * from './lib/icons/stamp-icon/stamp-icon.component';
 export * from './lib/instant-search/instant-search/instant-search.component';
-export * from './lib/popup/popup-layer/popup-layer.component';
 export * from './lib/popup/popup-list-item/popup-list-item.component';
+export * from './lib/popup/popup/popup.component';
 export * from './lib/testbtn/testbtn.component';
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.component.spec.ts b/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.component.spec.ts
deleted file mode 100644
index 2339d08c85d4982936b7376995b145aa8518310c..0000000000000000000000000000000000000000
--- a/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.component.spec.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { getElementFromFixture } from '@alfa-client/test-utils';
-import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
-import { PopupLayerComponent } from './popup-layer.component';
-
-describe('PopupLayerComponent', () => {
-  let component: PopupLayerComponent;
-  let fixture: ComponentFixture<PopupLayerComponent>;
-  const popupButton: string = getDataTestIdOf('popup-button');
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [PopupLayerComponent],
-    }).compileComponents();
-
-    fixture = TestBed.createComponent(PopupLayerComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-
-  describe('toggleShowPopupList', () => {
-    it('should change false to true', () => {
-      component.showPopupList = false;
-
-      component.toggleShowPopupList();
-
-      expect(component.showPopupList).toBe(true);
-    });
-
-    it('should change true to false', () => {
-      component.showPopupList = true;
-
-      component.toggleShowPopupList();
-
-      expect(component.showPopupList).toBe(false);
-    });
-  });
-
-  describe('aria-expanded', () => {
-    it('should be true if popup is opened', () => {
-      component.showPopupList = true;
-      fixture.detectChanges();
-
-      const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
-
-      expect(buttonElement.getAttribute('aria-expanded')).toBe('true');
-    });
-
-    it('should be false if popup is closed', () => {
-      component.showPopupList = false;
-      fixture.detectChanges();
-
-      const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
-
-      expect(buttonElement.getAttribute('aria-expanded')).toBe('false');
-    });
-  });
-
-  describe('toggleClicked', () => {
-    beforeEach(() => {
-      component.popupListRef = {
-        nativeElement: {
-          focus: jest.fn(),
-        },
-      };
-    });
-
-    it('should toggle popup list', () => {
-      component.toggleShowPopupList = jest.fn();
-
-      component.toggleClicked();
-
-      expect(component.toggleShowPopupList).toHaveBeenCalled();
-    });
-
-    it('should focus popup list if it is shown', fakeAsync(() => {
-      component.toggleClicked();
-      tick();
-
-      expect(component.popupListRef.nativeElement.focus).toHaveBeenCalled();
-    }));
-
-    it('should focus popup list if it is hidden', fakeAsync(() => {
-      component.showPopupList = true;
-
-      component.toggleClicked();
-      tick();
-
-      expect(component.popupListRef.nativeElement.focus).not.toHaveBeenCalled();
-    }));
-  });
-});
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.component.ts b/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.component.ts
deleted file mode 100644
index d17ca93f0bf6939b9a4d58e4b48d1f29794b7b4c..0000000000000000000000000000000000000000
--- a/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.component.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { CommonModule } from '@angular/common';
-import { Component, ElementRef, Input, ViewChild } from '@angular/core';
-
-@Component({
-  selector: 'ods-popup-layer',
-  standalone: true,
-  imports: [CommonModule],
-  template: `<div class="relative w-fit">
-    <button
-      class="w-fit"
-      (click)="toggleClicked()"
-      [attr.aria-expanded]="showPopupList"
-      aria-haspopup="true"
-      [attr.aria-label]="label"
-      data-test-id="popup-button"
-    >
-      <ng-content select="[button]" />
-    </button>
-    <ul
-      *ngIf="showPopupList"
-      class="animate-fadeIn absolute min-w-44 max-w-80 rounded shadow-lg shadow-grayborder focus:outline-none"
-      [ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'"
-      role="dialog"
-      aria-modal="true"
-      tabindex="0"
-      #popupList
-    >
-      <ng-content />
-    </ul>
-  </div>`,
-})
-export class PopupLayerComponent {
-  @Input() alignTo: 'left' | 'right' = 'left';
-  @Input() label: string = '';
-
-  @ViewChild('popupList') popupListRef: ElementRef;
-
-  showPopupList: boolean = false;
-
-  toggleShowPopupList(): void {
-    this.showPopupList = !this.showPopupList;
-  }
-
-  toggleClicked(): void {
-    this.toggleShowPopupList();
-    if (this.showPopupList) {
-      setTimeout(() => this.popupListRef.nativeElement.focus());
-    }
-  }
-}
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.stories.ts b/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.stories.ts
deleted file mode 100644
index 87df3b7f66189d82a83a34f9bc30957d06abeca0..0000000000000000000000000000000000000000
--- a/alfa-client/libs/design-system/src/lib/popup/popup-layer/popup-layer.stories.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
-  argsToTemplate,
-  componentWrapperDecorator,
-  moduleMetadata,
-  type Meta,
-  type StoryObj,
-} from '@storybook/angular';
-
-import { PopupListItemComponent } from '../popup-list-item/popup-list-item.component';
-import { PopupLayerComponent } from './popup-layer.component';
-
-const meta: Meta<PopupLayerComponent> = {
-  title: 'Popup/Popup layer',
-  component: PopupLayerComponent,
-  decorators: [
-    moduleMetadata({
-      imports: [PopupLayerComponent, PopupListItemComponent],
-    }),
-    componentWrapperDecorator((story) => `<div class="flex justify-center mb-20">${story}</div>`),
-  ],
-  excludeStories: /.*Data$/,
-  tags: ['autodocs'],
-};
-
-export default meta;
-type Story = StoryObj<PopupLayerComponent>;
-
-export const Default: Story = {
-  args: { alignTo: 'left' },
-  argTypes: {
-    alignTo: {
-      control: 'select',
-      options: ['left', 'right'],
-      table: {
-        defaultValue: { summary: 'left' },
-      },
-    },
-  },
-  render: (args) => ({
-    props: args,
-    template: `<ods-popup-layer ${argsToTemplate(args)}>
-        <p button>Trigger popup</p>
-        <ods-popup-list-item caption="Lorem" />
-        <ods-popup-list-item caption="Ipsum" />
-      </ods-popup-layer>`,
-  }),
-};
-
-export const LongText: Story = {
-  render: (args) => ({
-    props: args,
-    template: `<ods-popup-layer ${argsToTemplate(args)}>
-        <p button>Trigger popup</p>
-        <ods-popup-list-item caption="Lorem" />
-        <ods-popup-list-item caption="Lorem ipsum dolor sit amet" />
-      </ods-popup-layer>`,
-  }),
-};
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.spec.ts b/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.spec.ts
index 87b71e48a6416f8ea76cdcf0a1c9d5eafb4041bb..3d624f9674b26a1f03a2e024f0139f9eb8230746 100644
--- a/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.spec.ts
+++ b/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.spec.ts
@@ -21,7 +21,7 @@ describe('PopupListItemComponent', () => {
   });
 
   describe('itemClicked emitter', () => {
-    it('should emit clickItem', () => {
+    it('should emit itemClicked', () => {
       component.itemClicked.emit = jest.fn();
 
       dispatchEventFromFixture(fixture, 'button', 'click');
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f2a64dec5c9d60e36c292b55c6ae4a1b8d414f16
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts
@@ -0,0 +1,587 @@
+import { getElementFromFixture } from '@alfa-client/test-utils';
+import { ElementRef } from '@angular/core';
+import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
+import { PopupComponent } from './popup.component';
+
+describe('PopupComponent', () => {
+  let component: PopupComponent;
+  let fixture: ComponentFixture<PopupComponent>;
+  const popupButton: string = getDataTestIdOf('popup-button');
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [PopupComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(PopupComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('toggleShowPopupList', () => {
+    it('should change false to true', () => {
+      component.showPopupList = false;
+
+      component.toggleShowPopupList();
+
+      expect(component.showPopupList).toBe(true);
+    });
+
+    it('should change true to false', () => {
+      component.showPopupList = true;
+
+      component.toggleShowPopupList();
+
+      expect(component.showPopupList).toBe(false);
+    });
+  });
+
+  describe('aria-expanded', () => {
+    it('should be true if popup is opened', () => {
+      component.showPopupList = true;
+      fixture.detectChanges();
+
+      const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
+
+      expect(buttonElement.getAttribute('aria-expanded')).toBe('true');
+    });
+
+    it('should be false if popup is closed', () => {
+      component.showPopupList = false;
+      fixture.detectChanges();
+
+      const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton);
+
+      expect(buttonElement.getAttribute('aria-expanded')).toBe('false');
+    });
+  });
+
+  describe('toggleClicked', () => {
+    beforeEach(async () => {
+      component.showPopupList = true;
+      fixture.detectChanges();
+      await fixture.whenStable();
+      jest.spyOn(component.popupListRef.nativeElement, 'focus');
+    });
+
+    it('should toggle popup list', () => {
+      component.toggleShowPopupList = jest.fn();
+
+      component.toggleClicked();
+
+      expect(component.toggleShowPopupList).toHaveBeenCalled();
+    });
+
+    it('should focus popup list if it is shown', fakeAsync(() => {
+      component.showPopupList = false;
+
+      component.toggleClicked();
+      tick();
+
+      expect(component.popupListRef.nativeElement.focus).toHaveBeenCalled();
+    }));
+
+    it('should not focus popup list if it is hidden', fakeAsync(() => {
+      component.showPopupList = true;
+
+      component.toggleClicked();
+      tick();
+
+      expect(component.popupListRef.nativeElement.focus).not.toHaveBeenCalled();
+    }));
+  });
+
+  describe('isTabKey', () => {
+    it('should return true', () => {
+      const tabKeyEvent: KeyboardEvent = { ...new KeyboardEvent('tab'), key: 'Tab' };
+
+      const result: boolean = component.isTabKey(tabKeyEvent);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false', () => {
+      const keyEvent: KeyboardEvent = new KeyboardEvent('whatever');
+
+      const result: boolean = component.isTabKey(keyEvent);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('isShiftTabKey', () => {
+    it('should return true', () => {
+      const shiftTabKeyEvent: KeyboardEvent = {
+        ...new KeyboardEvent('tab'),
+        key: 'Tab',
+        shiftKey: true,
+      };
+
+      const result: boolean = component.isShiftTabKey(shiftTabKeyEvent);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false', () => {
+      const tabKeyEvent: KeyboardEvent = { ...new KeyboardEvent('tab'), key: 'Tab' };
+
+      const result: boolean = component.isShiftTabKey(tabKeyEvent);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('isEscapeKey', () => {
+    it('should return true', () => {
+      const escapeKeyEvent: KeyboardEvent = {
+        ...new KeyboardEvent('esc'),
+        key: 'Escape',
+      };
+
+      const result: boolean = component.isEscapeKey(escapeKeyEvent);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false', () => {
+      const keyEvent: KeyboardEvent = new KeyboardEvent('whatever');
+
+      const result: boolean = component.isEscapeKey(keyEvent);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('hidePopupListAndFocusButton', () => {
+    beforeEach(() => {
+      jest.spyOn(component.popupButtonRef.nativeElement, 'focus');
+    });
+    it('should hide popup list', () => {
+      component.showPopupList = true;
+
+      component.hidePopupListAndFocusButton();
+
+      expect(component.showPopupList).toBe(false);
+    });
+
+    it('should focus button', () => {
+      component.showPopupList = true;
+
+      component.hidePopupListAndFocusButton();
+
+      expect(component.popupButtonRef.nativeElement.focus).toHaveBeenCalled();
+    });
+  });
+
+  describe('isPopupListHidden', () => {
+    it('should return true', () => {
+      component.showPopupList = false;
+
+      const result: boolean = component.isPopupListHidden();
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false', () => {
+      component.showPopupList = true;
+
+      const result: boolean = component.isPopupListHidden();
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('isFirstItemFocused', () => {
+    const document: Document = new Document();
+    const firstElement: HTMLElement = document.createElement('a');
+    const lastElement: HTMLElement = document.createElement('b');
+    const element: HTMLElement = document.createElement('div');
+    element.appendChild(firstElement);
+    element.appendChild(lastElement);
+    const popupList: ElementRef<HTMLElement> = {
+      nativeElement: element,
+    };
+
+    it('should return true', () => {
+      const documentWithFirstFocused = { ...document, activeElement: firstElement };
+
+      const result: boolean = component.isFirstItemFocused(popupList, documentWithFirstFocused);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false if popup list is undefined', () => {
+      const documentWithNoFocused: Document = new Document();
+
+      const result: boolean = component.isFirstItemFocused(undefined, documentWithNoFocused);
+
+      expect(result).toBe(false);
+    });
+
+    it('should return false if first element is not focused', () => {
+      const documentWithLastFocused = { ...document, activeElement: lastElement };
+
+      const result: boolean = component.isFirstItemFocused(popupList, documentWithLastFocused);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('isLastItemFocused', () => {
+    const document: Document = new Document();
+    const firstElement: HTMLElement = document.createElement('a');
+    const lastElement: HTMLElement = document.createElement('b');
+    const element: HTMLElement = document.createElement('div');
+    element.appendChild(firstElement);
+    element.appendChild(lastElement);
+    const popupList: ElementRef<HTMLElement> = {
+      nativeElement: element,
+    };
+
+    it('should return true', () => {
+      const documentWithLastFocused = { ...document, activeElement: lastElement };
+
+      const result: boolean = component.isLastItemFocused(popupList, documentWithLastFocused);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false if popup list is undefined', () => {
+      const documentWithNoFocused: Document = new Document();
+
+      const result: boolean = component.isLastItemFocused(undefined, documentWithNoFocused);
+
+      expect(result).toBe(false);
+    });
+
+    it('should return false if last element is not focused', () => {
+      const documentWithFirstFocused = { ...document, activeElement: firstElement };
+
+      const result: boolean = component.isLastItemFocused(popupList, documentWithFirstFocused);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('isPopupListFocused', () => {
+    let document: Document = new Document();
+    const element: HTMLElement = document.createElement('div');
+    const popupList: ElementRef<HTMLElement> = {
+      nativeElement: element,
+    };
+
+    it('should return true', () => {
+      const documentWithListFocused = { ...document, activeElement: element };
+
+      const result: boolean = component.isPopupListFocused(popupList, documentWithListFocused);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false if popup list is undefined', () => {
+      const documentWithNoFocused: Document = new Document();
+
+      const result: boolean = component.isPopupListFocused(undefined, documentWithNoFocused);
+
+      expect(result).toBe(false);
+    });
+
+    it('should return false if popup list is not focused', () => {
+      const documentWithNoFocused: Document = new Document();
+
+      const result: boolean = component.isPopupListFocused(popupList, documentWithNoFocused);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('focusFirstItem', () => {
+    const document: Document = new Document();
+    const listElement: HTMLElement = document.createElement('ul');
+
+    const firstElement: HTMLElement = document.createElement('a');
+    const lastElement: HTMLElement = document.createElement('b');
+    const buttonElement: HTMLElement = document.createElement('button');
+    buttonElement.focus = jest.fn();
+    firstElement.appendChild(buttonElement);
+
+    listElement.appendChild(firstElement);
+    listElement.appendChild(lastElement);
+    const popupList: ElementRef<HTMLElement> = {
+      nativeElement: listElement,
+    };
+
+    it('should get focusable element', () => {
+      component.getFocusableElement = jest.fn();
+
+      component.focusFirstItem(popupList);
+
+      expect(component.getFocusableElement).toHaveBeenCalled();
+    });
+
+    it('should focus first child', fakeAsync(() => {
+      component.focusFirstItem(popupList);
+      tick();
+
+      expect(buttonElement.focus).toHaveBeenCalled();
+    }));
+  });
+
+  describe('focusLastItem', () => {
+    const document: Document = new Document();
+    const listElement: HTMLElement = document.createElement('ul');
+
+    const firstElement: HTMLElement = document.createElement('a');
+    const lastElement: HTMLElement = document.createElement('b');
+    const buttonElement: HTMLElement = document.createElement('button');
+    buttonElement.focus = jest.fn();
+    lastElement.appendChild(buttonElement);
+
+    listElement.appendChild(firstElement);
+    listElement.appendChild(lastElement);
+    const popupList: ElementRef<HTMLElement> = {
+      nativeElement: listElement,
+    };
+
+    it('should get focusable element', () => {
+      component.getFocusableElement = jest.fn();
+
+      component.focusLastItem(popupList);
+
+      expect(component.getFocusableElement).toHaveBeenCalled();
+    });
+
+    it('should focus first child', fakeAsync(() => {
+      component.focusLastItem(popupList);
+      tick();
+
+      expect(buttonElement.focus).toHaveBeenCalled();
+    }));
+  });
+
+  describe('shouldFocusFirstItem', () => {
+    const keyboardEvent = new KeyboardEvent('e');
+
+    it('should return true if is tab key and last item is focused', () => {
+      component.isTabKey = jest.fn().mockReturnValue(true);
+      component.isLastItemFocused = jest.fn().mockReturnValue(true);
+
+      const result: boolean = component.shouldFocusFirstItem(keyboardEvent);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false', () => {
+      const result: boolean = component.shouldFocusFirstItem(keyboardEvent);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('shouldFocusLastItem', () => {
+    const keyboardEvent = new KeyboardEvent('e');
+
+    it('should return true if is shift tab key and first item is focused', () => {
+      component.isShiftTabKey = jest.fn().mockReturnValue(true);
+      component.isFirstItemFocused = jest.fn().mockReturnValue(true);
+
+      const result: boolean = component.shouldFocusLastItem(keyboardEvent);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return true if is shift tab key and popup list is focused', () => {
+      component.isShiftTabKey = jest.fn().mockReturnValue(true);
+      component.isPopupListFocused = jest.fn().mockReturnValue(true);
+
+      const result: boolean = component.shouldFocusLastItem(keyboardEvent);
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false', () => {
+      const result: boolean = component.shouldFocusLastItem(keyboardEvent);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('getFocusableElement', () => {
+    const document: Document = new Document();
+    const element: HTMLElement = document.createElement('div');
+    const buttonElement = document.createElement('button');
+    element.appendChild(buttonElement);
+
+    it('should return focusable element', () => {
+      const result: HTMLElement = component.getFocusableElement(element);
+
+      expect(result).toBe(buttonElement);
+    });
+  });
+
+  describe('onClickHandler', () => {
+    const e: MouseEvent = new MouseEvent('test');
+
+    beforeEach(() => {
+      component.hidePopupListAndFocusButton = jest.fn();
+    });
+
+    it('should not handle click if popup is hidden', () => {
+      component.isPopupListHidden = jest.fn().mockReturnValue(true);
+
+      component.onClickHandler(e);
+
+      expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
+    });
+
+    it('should hide popup and focus button if clicked outside of button', () => {
+      component.isPopupListHidden = jest.fn().mockReturnValue(false);
+
+      component.onClickHandler(e);
+
+      expect(component.hidePopupListAndFocusButton).toHaveBeenCalled();
+    });
+
+    it('should not handle click if clicked inside of button', () => {
+      component.isPopupListHidden = jest.fn().mockReturnValue(false);
+      component.popupButtonRef.nativeElement.contains = jest.fn().mockReturnValue(true);
+
+      component.onClickHandler(e);
+
+      expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('onKeydownHandler', () => {
+    const keyboardEvent: KeyboardEvent = new KeyboardEvent('e');
+
+    beforeEach(() => {
+      component.isPopupListHidden = jest.fn();
+      component.isTabKey = jest.fn();
+      component.isShiftTabKey = jest.fn();
+      component.isEscapeKey = jest.fn();
+      component.isFirstItemFocused = jest.fn();
+      component.isLastItemFocused = jest.fn();
+      component.focusFirstItem = jest.fn();
+      component.focusLastItem = jest.fn();
+      component.isPopupListFocused = jest.fn();
+      component.shouldFocusFirstItem = jest.fn();
+      component.shouldFocusLastItem = jest.fn();
+      component.hidePopupListAndFocusButton = jest.fn();
+    });
+
+    it('should check for hidden popup list', () => {
+      component.onKeydownHandler(keyboardEvent);
+
+      expect(component.isPopupListHidden).toHaveBeenCalled();
+    });
+
+    describe('popup list is hidden', () => {
+      beforeEach(() => {
+        component.isPopupListHidden = jest.fn().mockReturnValue(true);
+      });
+
+      it('should not focus first item', () => {
+        component.onKeydownHandler(keyboardEvent);
+
+        expect(component.focusFirstItem).not.toHaveBeenCalled();
+      });
+
+      it('should not focus last item', () => {
+        component.onKeydownHandler(keyboardEvent);
+
+        expect(component.focusLastItem).not.toHaveBeenCalled();
+      });
+
+      it('should ignore escape key handling', () => {
+        component.onKeydownHandler(keyboardEvent);
+
+        expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
+      });
+    });
+
+    describe('popup list is visible', () => {
+      beforeEach(() => {
+        component.isPopupListHidden = jest.fn().mockReturnValue(false);
+      });
+
+      it('should check if should focus first item', () => {
+        component.onKeydownHandler(keyboardEvent);
+
+        expect(component.shouldFocusFirstItem).toHaveBeenCalled();
+      });
+
+      it('should focus first item', () => {
+        component.shouldFocusFirstItem = jest.fn().mockReturnValue(true);
+
+        component.onKeydownHandler(keyboardEvent);
+
+        expect(component.focusFirstItem).toHaveBeenCalled();
+      });
+
+      it('should not focus first item', () => {
+        component.onKeydownHandler(keyboardEvent);
+
+        expect(component.focusFirstItem).not.toHaveBeenCalled();
+      });
+
+      describe('should not focus first item', () => {
+        beforeEach(() => {
+          component.shouldFocusFirstItem = jest.fn().mockReturnValue(false);
+        });
+
+        it('should check if should focus last item', () => {
+          component.onKeydownHandler(keyboardEvent);
+
+          expect(component.shouldFocusLastItem).toHaveBeenCalled();
+        });
+
+        it('should focus last item', () => {
+          component.shouldFocusLastItem = jest.fn().mockReturnValue(true);
+
+          component.onKeydownHandler(keyboardEvent);
+
+          expect(component.focusLastItem).toHaveBeenCalled();
+        });
+
+        it('should not focus last item', () => {
+          component.onKeydownHandler(keyboardEvent);
+
+          expect(component.focusLastItem).not.toHaveBeenCalled();
+        });
+
+        describe('should not focus last item', () => {
+          beforeEach(() => {
+            component.shouldFocusFirstItem = jest.fn().mockReturnValue(false);
+            component.shouldFocusLastItem = jest.fn().mockReturnValue(false);
+          });
+
+          it('should check for escape key', () => {
+            component.onKeydownHandler(keyboardEvent);
+
+            expect(component.isEscapeKey).toHaveBeenCalled();
+          });
+
+          it('should hide popup list', () => {
+            component.isEscapeKey = jest.fn().mockReturnValue(true);
+
+            component.onKeydownHandler(keyboardEvent);
+
+            expect(component.hidePopupListAndFocusButton).toHaveBeenCalled();
+          });
+
+          it('should not hide popup list', () => {
+            component.onKeydownHandler(keyboardEvent);
+
+            expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled();
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da4cb9da07265f7e0b37d5f5d5efa7b3b2def16e
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts
@@ -0,0 +1,139 @@
+import { CommonModule } from '@angular/common';
+import { Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';
+import { first, last } from 'lodash-es';
+import { twMerge } from 'tailwind-merge';
+
+@Component({
+  selector: 'ods-popup',
+  standalone: true,
+  imports: [CommonModule],
+  template: `<div class="relative w-fit">
+    <button
+      class="w-fit outline-2 outline-offset-2 outline-focus"
+      [ngClass]="[twMerge('w-fit outline-2 outline-offset-2 outline-focus', buttonClass)]"
+      (click)="toggleClicked()"
+      [attr.aria-expanded]="showPopupList"
+      aria-haspopup="true"
+      [attr.aria-label]="label"
+      data-test-id="popup-button"
+      #popupButton
+    >
+      <ng-content select="[button]" />
+    </button>
+    <ul
+      *ngIf="showPopupList"
+      class="absolute max-h-120 min-w-44 max-w-80 animate-fadeIn overflow-y-auto rounded shadow-lg shadow-grayborder focus:outline-none"
+      [ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'"
+      role="dialog"
+      aria-modal="true"
+      tabindex="0"
+      #popupList
+    >
+      <ng-content />
+    </ul>
+  </div>`,
+})
+export class PopupComponent {
+  @Input() alignTo: 'left' | 'right' = 'left';
+  @Input() label: string = '';
+  @Input() buttonClass: string = '';
+
+  constructor(public ref: ElementRef) {}
+
+  showPopupList: boolean = false;
+  twMerge = twMerge;
+
+  @ViewChild('popupList') popupListRef: ElementRef<HTMLUListElement>;
+  @ViewChild('popupButton') popupButtonRef: ElementRef<HTMLButtonElement>;
+
+  @HostListener('document:keydown', ['$event'])
+  onKeydownHandler(e: KeyboardEvent): void {
+    if (this.isPopupListHidden()) return;
+    if (this.shouldFocusFirstItem(e)) this.focusFirstItem(this.popupListRef);
+    if (this.shouldFocusLastItem(e)) this.focusLastItem(this.popupListRef);
+    if (this.isEscapeKey(e)) this.hidePopupListAndFocusButton();
+  }
+
+  @HostListener('document:click', ['$event'])
+  onClickHandler(e: MouseEvent): void {
+    if (this.isPopupListHidden()) return;
+    if (!this.popupButtonRef.nativeElement.contains(e.target as HTMLElement)) {
+      this.hidePopupListAndFocusButton();
+    }
+  }
+
+  toggleShowPopupList(): void {
+    this.showPopupList = !this.showPopupList;
+  }
+
+  hidePopupListAndFocusButton(): void {
+    this.showPopupList = false;
+    this.popupButtonRef.nativeElement.focus();
+  }
+
+  toggleClicked(): void {
+    this.toggleShowPopupList();
+    if (this.showPopupList) {
+      setTimeout(() => this.popupListRef.nativeElement.focus());
+    }
+  }
+
+  isTabKey(e: KeyboardEvent): boolean {
+    return e.key === 'Tab' && !e.shiftKey;
+  }
+
+  isShiftTabKey(e: KeyboardEvent): boolean {
+    return e.key === 'Tab' && Boolean(e.shiftKey);
+  }
+
+  isEscapeKey(e: KeyboardEvent): boolean {
+    return e.key === 'Escape';
+  }
+
+  isFirstItemFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean {
+    if (!popupList) return false;
+    return popupList.nativeElement.firstChild.contains(document.activeElement);
+  }
+
+  isPopupListFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean {
+    if (!popupList) return false;
+    return popupList.nativeElement.isSameNode(document.activeElement);
+  }
+
+  isLastItemFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean {
+    if (!popupList) return false;
+    return popupList.nativeElement.lastChild.contains(document.activeElement);
+  }
+
+  isPopupListHidden(): boolean {
+    return !this.showPopupList;
+  }
+
+  focusFirstItem(popupList: ElementRef<HTMLElement>): void {
+    const firstItem: HTMLElement = this.getFocusableElement(
+      first(popupList.nativeElement.children),
+    );
+    setTimeout(() => firstItem.focus());
+  }
+
+  focusLastItem(popupList: ElementRef<HTMLElement>): void {
+    const lastItem: HTMLElement = this.getFocusableElement(last(popupList.nativeElement.children));
+    setTimeout(() => lastItem.focus());
+  }
+
+  shouldFocusFirstItem(e: KeyboardEvent): boolean {
+    return this.isTabKey(e) && this.isLastItemFocused(this.popupListRef, document);
+  }
+
+  shouldFocusLastItem(e: KeyboardEvent): boolean {
+    return (
+      this.isShiftTabKey(e) &&
+      (this.isFirstItemFocused(this.popupListRef, document) ||
+        this.isPopupListFocused(this.popupListRef, document))
+    );
+  }
+
+  getFocusableElement(element: Element): HTMLElement {
+    return first(element.querySelectorAll('a[href], button, [tabindex]')) as HTMLElement;
+  }
+}
diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.stories.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.stories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ecde2c16145d2e95ddcd3327d51e003d27f721f6
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.stories.ts
@@ -0,0 +1,78 @@
+import {
+  argsToTemplate,
+  componentWrapperDecorator,
+  moduleMetadata,
+  type Meta,
+  type StoryObj,
+} from '@storybook/angular';
+
+import { SaveIconComponent } from '../../icons/save-icon/save-icon.component';
+import { UserIconComponent } from '../../icons/user-icon/user-icon.component';
+import { PopupListItemComponent } from '../popup-list-item/popup-list-item.component';
+import { PopupComponent } from './popup.component';
+
+const meta: Meta<PopupComponent> = {
+  title: 'Popup/Popup',
+  component: PopupComponent,
+  decorators: [
+    moduleMetadata({
+      imports: [PopupComponent, PopupListItemComponent, SaveIconComponent, UserIconComponent],
+    }),
+    componentWrapperDecorator((story) => `<div class="flex justify-center mb-32">${story}</div>`),
+  ],
+  excludeStories: /.*Data$/,
+  tags: ['autodocs'],
+};
+
+export default meta;
+type Story = StoryObj<PopupComponent>;
+
+export const Default: Story = {
+  args: { alignTo: 'left', label: '', buttonClass: '' },
+  argTypes: {
+    alignTo: {
+      control: 'select',
+      options: ['left', 'right'],
+      table: {
+        defaultValue: { summary: 'left' },
+      },
+    },
+    buttonClass: { description: 'Tailwind class for button' },
+    label: { description: 'Aria-label for button' },
+  },
+  render: (args) => ({
+    props: args,
+    template: `<ods-popup ${argsToTemplate(args)}>
+        <ods-user-icon button />
+        <ods-popup-list-item caption="Lorem" />
+        <ods-popup-list-item caption="Ipsum" />
+        <ods-popup-list-item caption="Dolor" />
+      </ods-popup>`,
+  }),
+};
+
+export const LongText: Story = {
+  render: (args) => ({
+    props: args,
+    template: `<ods-popup ${argsToTemplate(args)}>
+        <p button>Trigger popup</p>
+        <ods-popup-list-item caption="Lorem" />
+        <ods-popup-list-item caption="Lorem ipsum dolor sit amet" />
+      </ods-popup>`,
+  }),
+};
+
+export const ItemsWithIcons: Story = {
+  render: (args) => ({
+    props: args,
+    template: `<ods-popup ${argsToTemplate(args)}>
+        <p button>Trigger popup</p>
+        <ods-popup-list-item caption="Lorem">
+          <ods-save-icon icon size="small" />
+        </ods-popup-list-item>
+        <ods-popup-list-item caption="Lorem ipsum dolor sit amet">
+          <ods-save-icon icon size="small" />
+        </ods-popup-list-item>
+      </ods-popup>`,
+  }),
+};
diff --git a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js
index d9f3fc1776a0f7b225863cc6e7654f3c84539f74..2ad76e5342c66c35ccc434bb8b99b96135c961e7 100644
--- a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js
+++ b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js
@@ -45,6 +45,9 @@ module.exports = {
       borderWidth: {
         3: '3px',
       },
+      maxHeight: {
+        120: '480px',
+      },
       colors: {
         ozgblue: {
           50: 'hsl(200, 100%, 96%)',