diff --git a/.changeset/pf-alert.md b/.changeset/pf-alert.md
new file mode 100644
index 0000000000..f0642c0364
--- /dev/null
+++ b/.changeset/pf-alert.md
@@ -0,0 +1,15 @@
+---
+"@patternfly/elements": minor
+---
+
+### Minor Changes
+
+- Added `pf-alert` component for displaying alert messages of different types:
+ - Types: info, warning, danger, success, cogear, neutral, custom
+ - Features: optional heading, description, actions, dismiss button
+- Enables consistent alert messaging across apps and demos
+
+```html
+
+ This is a warning alert with optional description and actions.
+
\ No newline at end of file
diff --git a/elements/package.json b/elements/package.json
index 3f4e952bc7..549ab23384 100644
--- a/elements/package.json
+++ b/elements/package.json
@@ -15,6 +15,7 @@
"./pf-accordion/pf-accordion-header.js": "./pf-accordion/pf-accordion-header.js",
"./pf-accordion/pf-accordion-panel.js": "./pf-accordion/pf-accordion-panel.js",
"./pf-accordion/pf-accordion.js": "./pf-accordion/pf-accordion.js",
+ "./pf-alert/pf-alert.js": "./pf-alert/pf-alert.js",
"./pf-avatar/pf-avatar.js": "./pf-avatar/pf-avatar.js",
"./pf-back-to-top/pf-back-to-top.js": "./pf-back-to-top/pf-back-to-top.js",
"./pf-background-image/pf-background-image.js": "./pf-background-image/pf-background-image.js",
diff --git a/elements/pf-alert/README.md b/elements/pf-alert/README.md
new file mode 100644
index 0000000000..32622b8a0c
--- /dev/null
+++ b/elements/pf-alert/README.md
@@ -0,0 +1,38 @@
+# pf-alert
+
+The `pf-alert` web component displays PatternFly-styled alerts. It can be used inline in pages or as a toast notification. Alerts support several visual states (for example: `info`, `success`, `warning`, `danger`), an optional header slot, body content, and an `actions` slot for interactive controls.
+
+## Installation
+
+Import the element in your page or application as an ES module:
+
+```html
+
+```
+
+## Basic usage
+
+Inline alert example:
+
+```html
+
+ Success
+ The operation completed successfully.
+
+
+```
+
+Toast usage (static helper):
+
+```html
+
+```
diff --git a/elements/pf-alert/demo/custom-icon.html b/elements/pf-alert/demo/custom-icon.html
new file mode 100644
index 0000000000..20028e7a2d
--- /dev/null
+++ b/elements/pf-alert/demo/custom-icon.html
@@ -0,0 +1,32 @@
+
+
+ Success alert title
+
+
+ Success alert title
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elements/pf-alert/demo/expandable.html b/elements/pf-alert/demo/expandable.html
new file mode 100644
index 0000000000..6b23af11d2
--- /dev/null
+++ b/elements/pf-alert/demo/expandable.html
@@ -0,0 +1,47 @@
+
+
+ Success alert title
+ View details
+ lgnore
+
+
+
+ Success alert title (expanded)
+ Success alert description. This should tell the user more information about the alert.
+ View details
+ lgnore
+
+
+
+ Success alert title
+ View details
+ lgnore
+
+
+
+ Success alert title (expanded)
+ Success alert description. This should tell the user more information about the alert.
+ View details
+ lgnore
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elements/pf-alert/demo/inline-plain.html b/elements/pf-alert/demo/inline-plain.html
new file mode 100644
index 0000000000..afc68f4905
--- /dev/null
+++ b/elements/pf-alert/demo/inline-plain.html
@@ -0,0 +1,23 @@
+
+
+ Success alert title
+
+
+
+
+
\ No newline at end of file
diff --git a/elements/pf-alert/demo/inline-types.html b/elements/pf-alert/demo/inline-types.html
new file mode 100644
index 0000000000..a719ada220
--- /dev/null
+++ b/elements/pf-alert/demo/inline-types.html
@@ -0,0 +1,40 @@
+
+
+
+ Custom inline alert title
+
+
+
+ Info inline alert title
+
+
+
+ Success inline alert title
+
+
+
+ Warning inline alert title
+
+
+
+
+ Danger inline alert title
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elements/pf-alert/demo/inline-variations.html b/elements/pf-alert/demo/inline-variations.html
new file mode 100644
index 0000000000..8a3036009c
--- /dev/null
+++ b/elements/pf-alert/demo/inline-variations.html
@@ -0,0 +1,44 @@
+
+
+
+ Success alert title
+ Success alert description. This should tell the user more information about the alert.
+ View details
+ lgnore
+
+
+
+ Success alert title
+ Success alert description. This should tell the user more information about the alert.
+ This is
+ a link.
+
+
+
+
+ Success alert title
+ View details
+ lgnore
+
+
+
+ Success alert title
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elements/pf-alert/demo/types.html b/elements/pf-alert/demo/types.html
new file mode 100644
index 0000000000..cf1d6d36a1
--- /dev/null
+++ b/elements/pf-alert/demo/types.html
@@ -0,0 +1,42 @@
+
+
+
+ Default alert title
+
+
+
+ Info alert title
+
+
+
+ Success alert title
+
+
+
+ Warning alert title
+
+
+
+ Danger alert title
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elements/pf-alert/demo/variations.html b/elements/pf-alert/demo/variations.html
new file mode 100644
index 0000000000..c8cc02f6a3
--- /dev/null
+++ b/elements/pf-alert/demo/variations.html
@@ -0,0 +1,66 @@
+
+
+
+ Success alert title
+ Success alert description. This should tell the user more information about the alert.
+ View details
+ lgnore
+
+
+
+ Success alert title
+ Success alert description. This should tell the user more information about the alert. This is a link.
+
+
+
+ Success alert title
+ View details
+ lgnore
+
+
+
+ Success alert title
+
+
+
+ Success alert title
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur pellentesque neque cursus
+ enim
+ fringilla...
+ This example uses ".pf-m-truncate" to limit the title to a single line and truncate any overflow text with
+ ellipses.
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur pellentesque neque cursus
+ enim
+ fringilla tincidunt. Proin lobortis aliquam dictum. Nam vel ullamcorper nulla, nec blandit dolor. Vivamus
+ pellentesque...
+ This example uses ".pf-m-truncate" and sets "--pf-c-alert__title--max-lines: 2" to limit title to two lines and
+ truncate any overflow text with ellipses.
+
+
+
+
+
+
+
diff --git a/elements/pf-alert/docs/pf-alert.md b/elements/pf-alert/docs/pf-alert.md
new file mode 100644
index 0000000000..61f5ec0add
--- /dev/null
+++ b/elements/pf-alert/docs/pf-alert.md
@@ -0,0 +1,17 @@
+{% renderOverview %}
+
+{% endrenderOverview %}
+
+{% band header="Usage" %}{% endband %}
+
+{% renderSlots %}{% endrenderSlots %}
+
+{% renderAttributes %}{% endrenderAttributes %}
+
+{% renderMethods %}{% endrenderMethods %}
+
+{% renderEvents %}{% endrenderEvents %}
+
+{% renderCssCustomProperties %}{% endrenderCssCustomProperties %}
+
+{% renderCssParts %}{% endrenderCssParts %}
diff --git a/elements/pf-alert/pf-alert.css b/elements/pf-alert/pf-alert.css
new file mode 100644
index 0000000000..ec3d0214de
--- /dev/null
+++ b/elements/pf-alert/pf-alert.css
@@ -0,0 +1,148 @@
+
+header {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+#left-column pf-icon#icon {
+ margin-right: 0.5rem;
+}
+
+
+#container {
+ align-items: flex-start;
+ background-color: var(--_background-color);
+ border-width: var(--pf-global--BorderWidth--md);
+ border-style: solid;
+ border-color: var(--_border-color, var(--pf-global--default-color--200));
+ border-inline-start-color: transparent;
+ border-block-end-color: transparent;
+ border-inline-end-color: transparent;
+ margin-bottom: 1.5rem;
+ padding: var(--pf-global--spacer--md);
+ display: grid;
+ grid-template-columns: max-content 1fr max-content;
+ grid-template-areas:
+ "icon title action"
+ ". description description"
+ ". actiongroup actiongroup";
+ gap: var(--pf-global--spacer--xs);
+ font-family: var(--pf-global--FontFamily--text, RedHatText, 'Red Hat Text', Helvetica, Arial, sans-serif);
+ font-size: var(--pf-global--FontSize--sm);
+ line-height: var(--pf-global--lineHeight--md);
+ max-width: var(--pf-c-alert--MaxWidth, initial);
+ box-shadow: var(--_box-shadow);
+
+ & header ::slotted(*) {
+ font-family: var(--pf-global--FontFamily--text, RedHatText, 'Red Hat Text', Helvetica, Arial, sans-serif) !important;
+ font-size: var(--pf-global--FontSize--sm) !important;
+ line-height: var(--pf-global--lineHeight--md) !important;
+ margin: 0 !important;
+ }
+
+}
+
+#container.info {
+ --_border-color: var(--pf-global--palette--purple-500,
+ #2b9af3);
+ --_icon-color: var(--pf-global--palette--purple-500,
+ #2b9af3);
+ --title--Color: var(--pf-global--palette--purple-500,
+ #002952);
+ --_background-color: var(--pf-global--palette--purple-50,
+ #e7f1fa);
+}
+
+#container.cogear {
+ --_border-color: var(--pf-global--success-color--100,
+ #3e8635);
+ --_icon-color: var(--pf-global--success-color--100,
+ #3e8635);
+ --title--Color: var(--pf-global--palette--purple-500,
+ #1e4f18);
+ --_background-color: var(--pf-global--palette--green-50,
+ #f3faf2);
+}
+
+#container.success {
+ --_border-color: var(--pf-global--success-color--100,
+ #3e8635);
+ --_icon-color: var(--pf-global--success-color--100,
+ #3e8635);
+ --title--Color: var(--pf-global--palette--purple-500,
+ #1e4f18);
+ --_background-color: var(--pf-global--palette--green-50,
+ #f3faf2);
+}
+
+#container.warning {
+ --_border-color: var(--pf-global--warning-color--100,
+ #f0ab00);
+ --_icon-color: var(--pf-global--warning-color--100,
+ #f0ab00);
+ --title--Color: var(--pf-global--palette--purple-500,
+ #795600);
+ --_background-color: var(--pf-global--palette--gold-50,
+ #fdf7e7);
+}
+
+#container.custom {
+ --_background-color: var(--pf-global--BackgroundColor--100,
+ #f2f9f9);
+ --_border-color: var(--pf-global--BorderColor--100,
+ #009596);
+ --_icon-color: var(--pf-global--icon--Color--light,
+ #009596);
+ --title--Color: var(--pf-global--palette--gold-50,
+ #003737);
+
+}
+
+#container.danger {
+ --_border-color: var(--pf-global--danger-color--200,
+ #c9190b);
+ --_icon-color: var(--pf-global--danger-color--200,
+ #c9190b);
+ --title--Color: var(--pf-global--palette--purple-500,
+ #a30000);
+ --_background-color: var(--pf-global--palette--red-50,
+ #faeae8);
+}
+#container header ::slotted(h3) {
+ font-weight: bold;
+ color: var(--title--Color);
+}
+
+
+
+#header {
+ flex: 1 1 auto;
+}
+
+#icon {
+ --pf-icon--size: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--_icon-color);
+}
+
+#left-column pf-icon#arrow-icon,
+#header-actions pf-icon#close-button {
+ --pf-icon--size: 16px;
+ --pf-c-icon--Color: #6a6e73;
+ color: #6a6e73;
+ cursor: pointer;
+ transition: color 0.2s ease, --pf-c-icon--Color 0.2s ease;
+}
+
+#left-column pf-icon#arrow-icon:hover,
+#left-column pf-icon#arrow-icon:active,
+#left-column pf-icon#arrow-icon.active,
+#header-actions pf-icon#close-button:hover,
+#header-actions pf-icon#close-button:active,
+#header-actions pf-icon#close-button.active {
+ --pf-c-icon--Color: #000000;
+ color: #000000;
+}
\ No newline at end of file
diff --git a/elements/pf-alert/pf-alert.ts b/elements/pf-alert/pf-alert.ts
new file mode 100644
index 0000000000..b69aeea0f5
--- /dev/null
+++ b/elements/pf-alert/pf-alert.ts
@@ -0,0 +1,189 @@
+import { LitElement, type TemplateResult, html, isServer } from 'lit';
+import { customElement } from 'lit/decorators/custom-element.js';
+import { property } from 'lit/decorators/property.js';
+import { classMap } from 'lit/directives/class-map.js';
+import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js';
+import styles from './pf-alert.css';
+import '@patternfly/elements/pf-icon/pf-icon.js';
+import '@patternfly/elements/pf-button/pf-button.js';
+
+
+interface AlertAction {
+ action: 'dismiss' | 'confirm' | string;
+ text: string;
+}
+interface ToastOptions {
+ id?: string;
+ message: string | TemplateResult;
+ heading?: string;
+ state?: PfAlert['status'];
+ persistent?: boolean;
+ actions?: AlertAction[];
+}
+const ICONS = new Map(Object.entries({
+ neutral: 'minus-circle',
+ info: 'info-circle',
+ success: 'check-circle',
+ custom: 'bell',
+ cogear: 'cog',
+ warning: 'exclamation-triangle',
+ danger: 'exclamation-circle',
+ close: 'times',
+
+}));
+
+
+export class AlertCloseEvent extends Event {
+ constructor(public action: 'close' | 'confirm' | 'dismiss' | string) {
+ super('close', { bubbles: true, cancelable: true });
+ }
+}
+// let toaster: HTMLElement;
+const toasts = new Set>();
+
+
+@customElement('pf-alert')
+export class PfAlert extends LitElement {
+ static readonly styles: CSSStyleSheet[] = [styles];
+
+ @property({ reflect: true })
+ status:
+ | 'warning'
+ | 'custom'
+ | 'neutral'
+ | 'info'
+ | 'success'
+ | 'danger' = 'neutral';
+
+ @property({ reflect: true }) variant?: 'alternate' | 'inline';
+
+ @property({ reflect: true, type: Boolean }) dismissable = false;
+
+ #slots = new SlotController(this, 'header', null, 'actions');
+
+ get #icon() {
+ const internalStatus = this.closest('.demo-with-arrows')
+ && this.status === 'neutral' && this.classList.contains('cogear-demo') ?
+ 'cogear'
+ : this.status;
+ switch (internalStatus) {
+ // @ts-expect-error: support for deprecated props
+ case 'note': return ICONS.get('info');
+ // @ts-expect-error: support for deprecated props
+ case 'default': return ICONS.get('neutral');
+ // @ts-expect-error: support for deprecated props
+ case 'error': return ICONS.get('danger');
+ default: return ICONS.get(internalStatus);
+ }
+ }
+
+ // #aliasState(state: string) {
+ // switch (state.toLowerCase()) {
+ // // the first three are deprecated pre-DPO status names
+ // case 'note': return 'info';
+ // case 'default': return 'neutral';
+ // case 'error': return 'danger';
+ // // the following are DPO-approved status names
+ // case 'danger':
+ // case 'warning':
+ // case 'custom':
+ // case 'neutral':
+ // case 'info':
+ // case 'success':
+ // case 'cogear':
+ // return state.toLowerCase() as this['status'];
+ // default:
+ // return 'neutral';
+ // }
+ // }
+
+ connectedCallback(): void {
+ super.connectedCallback();
+ if (!isServer) {
+ this.requestUpdate();
+ }
+ }
+
+ render(): TemplateResult<1> {
+ const _isServer = isServer && !this.hasUpdated;
+ const hasActions = _isServer || this.#slots.hasSlotted('actions');
+ const hasBody =
+ _isServer || this.#slots.hasSlotted(SlotController.default as unknown as string);
+ const { variant = 'inline' } = this;
+ // const state = this.#aliasState(this.status);
+ const inDemo = this.closest('.demo-with-arrows') !== null;
+ const hasDescription = this.querySelector('p') !== null;
+ const showArrow = inDemo;
+ const internalStatus = (
+ this.closest('.demo-with-arrows')
+ && this.classList.contains('cogear-demo')) ?
+ 'cogear'
+ : this.status;
+ const arrowDirection = hasDescription ? 'angle-down' : 'angle-right';
+ const footer = html``;
+ return html`
+
+
+ ${showArrow ? html`
+
+ ` : ''}
+
+
+
+
+
+ ${!this.dismissable ? '' : html`
+ `}
+
+
+
+
+
+ ${footer}
+
+
+ `;
+ }
+}
+
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'pf-alert': PfAlert;
+ }
+}
diff --git a/elements/pf-alert/test/MANUAL_TESTS.md b/elements/pf-alert/test/MANUAL_TESTS.md
new file mode 100644
index 0000000000..bc066ff899
--- /dev/null
+++ b/elements/pf-alert/test/MANUAL_TESTS.md
@@ -0,0 +1,88 @@
+# pf-alert — Manual Tests
+
+This document outlines manual testing procedures for the `pf-alert` component.
+
+## Setup
+1. Start the development server:
+```bash
+npm run start
+```
+2. Open the demo page in your browser
+3. Have a screen reader ready (e.g., NVDA, VoiceOver)
+
+## Visual Tests
+
+### Base Alert
+- [ ] Renders with correct default styling
+- [ ] Title text is clearly visible
+- [ ] Content text is properly formatted
+- [ ] Icon matches the alert state
+- [ ] Close button (if dismissable) is properly positioned
+
+### States
+Test each state and verify proper styling:
+- [ ] Default
+- [ ] Success (green)
+- [ ] Warning (orange/yellow)
+- [ ] Danger (red)
+- [ ] Info (blue)
+
+### Variants
+Test each variant:
+- [ ] Inline
+- [ ] Toast
+- [ ] Default
+
+## Interaction Tests
+
+### Keyboard Navigation
+- [ ] Tab key focuses interactive elements in correct order
+- [ ] Enter/Space triggers action buttons
+- [ ] Escape key dismisses alert (if dismissable)
+- [ ] Focus is properly trapped in modal alerts (if applicable)
+
+### Mouse Interaction
+- [ ] Click on close button dismisses alert
+- [ ] Action buttons respond to clicks
+- [ ] Toast alerts can be dismissed via close button
+
+### Screen Reader Tests
+Using NVDA or VoiceOver:
+- [ ] Alert role is announced
+- [ ] Alert state (success/warning/etc) is announced
+- [ ] Title and content are read in correct order
+- [ ] Interactive elements are properly announced
+- [ ] Dismissal is announced
+
+## Functional Tests
+
+### Toast API
+- [ ] `PfAlert.toast()` creates visible toast
+- [ ] Toast appears in correct position
+- [ ] Auto-dismiss works with specified duration
+- [ ] Multiple toasts stack correctly
+
+### State Changes
+- [ ] Changing state updates styling immediately
+- [ ] Changing variant updates layout immediately
+- [ ] Adding/removing `dismissable` updates UI
+- [ ] Slot content updates reflect immediately
+
+## Accessibility Tests
+- [ ] Run aXe or similar tool
+- [ ] Verify color contrast meets WCAG standards
+- [ ] Verify all interactive elements are keyboard accessible
+- [ ] Check aria attributes are present and correct
+- [ ] Test with screen reader in different browsers
+
+## Browser Testing
+Test in:
+- [ ] Chrome
+- [ ] Firefox
+- [ ] Safari
+- [ ] Edge
+
+## Notes
+- Document any bugs or inconsistencies found
+- Note any browser-specific issues
+- Record accessibility concerns
\ No newline at end of file
diff --git a/elements/pf-alert/test/pf-alert.e2e.ts b/elements/pf-alert/test/pf-alert.e2e.ts
new file mode 100644
index 0000000000..82bff11ab3
--- /dev/null
+++ b/elements/pf-alert/test/pf-alert.e2e.ts
@@ -0,0 +1,137 @@
+import { test, expect } from '@playwright/test';
+import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js';
+import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js';
+
+const tagName = 'pf-alert';
+
+test.describe(tagName, () => {
+ test.beforeEach(async ({ page }) => {
+ const componentPage = new PfeDemoPage(page, tagName);
+ await componentPage.navigate();
+ });
+ test('snapshot', async ({ page }) => {
+ const componentPage = new PfeDemoPage(page, tagName);
+ await componentPage.navigate();
+ await componentPage.snapshot();
+ });
+
+ test('ssr', async ({ browser }) => {
+ const fixture = new SSRPage({
+ tagName,
+ browser,
+ demoDir: new URL('../demo/', import.meta.url),
+ importSpecifiers: [
+ `@patternfly/elements/${tagName}/${tagName}.js`,
+ ],
+ });
+ await fixture.snapshots();
+ });
+
+ test('keyboard navigation works correctly', async ({ page }) => {
+ const componentPage = new PfeDemoPage(page, tagName);
+ await componentPage.navigate();
+
+ // Start with focus on body
+ await page.focus('body');
+
+ // Tab should move focus to first interactive element
+ await page.keyboard.press('Tab');
+ const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
+ expect(focusedElement).toBeTruthy();
+
+ // If alert has actions, they should be focusable
+ const hasActions = await page.$('pf-alert [slot="actions"]');
+ if (hasActions) {
+ await page.keyboard.press('Tab');
+ const actionFocused = await page.evaluate(() =>
+ document.activeElement?.closest('[slot="actions"]') !== null
+ );
+ expect(actionFocused).toBeTruthy();
+ }
+ });
+
+ test('WAI-ARIA compliance', async ({ page }) => {
+ const componentPage = new PfeDemoPage(page, tagName);
+ await componentPage.navigate();
+
+ // Test inline alert role
+ const inlineAlert = await page.$('pf-alert[variant="inline"]');
+ if (inlineAlert) {
+ const role = await inlineAlert.getAttribute('role');
+ expect(role).toBe('alert');
+ }
+
+ // Test toast alert role
+ const toastAlert = await page.$('pf-alert[variant="toast"]');
+ if (toastAlert) {
+ const role = await toastAlert.getAttribute('role');
+ expect(role).toBe('status');
+ }
+
+ // Check dismissable alerts have proper close button
+ const dismissableAlert = await page.$('pf-alert[dismissable]');
+ if (dismissableAlert) {
+ const closeButton = await dismissableAlert.$('#close-button');
+ expect(await closeButton?.getAttribute('role')).toBe('button');
+ expect(await closeButton?.getAttribute('tabindex')).toBe('0');
+ expect(await closeButton?.getAttribute('aria-label')).toBe('Close');
+ }
+ });
+
+ test('accessibility - roles and attributes', async ({ page }) => {
+ // Test inline alert role
+ const inlineAlert = await page.locator('pf-alert[variant="inline"]').first();
+ if (await inlineAlert.count() > 0) {
+ expect(await inlineAlert.getAttribute('role')).toBe('alert');
+ }
+
+ // Test toast alert role
+ const toastAlert = await page.locator('pf-alert[variant="toast"]').first();
+ if (await toastAlert.count() > 0) {
+ expect(await toastAlert.getAttribute('role')).toBe('status');
+ }
+
+ // Verify aria attributes
+ const alert = await page.locator('pf-alert').first();
+ expect(await alert.getAttribute('aria-hidden')).not.toBe('true');
+ });
+
+ test('screen reader content structure', async ({ page }) => {
+ // Test header content is properly structured
+ const alertWithHeader = await page.locator('pf-alert:has([slot="header"])').first();
+ if (await alertWithHeader.count() > 0) {
+ const header = await alertWithHeader.locator('[slot="header"]');
+ expect(await header.count()).toBe(1);
+ }
+
+ // Test main content area
+ const alertWithContent = await page.locator('pf-alert:has(p)').first();
+ if (await alertWithContent.count() > 0) {
+ const content = await alertWithContent.locator('p');
+ expect(await content.count()).toBeGreaterThan(0);
+ }
+ });
+
+
+ test('visual statuses and variants', async ({ page }) => {
+ // Test each status renders
+ for (const status of ['success', 'warning', 'danger', 'info']) {
+ const alert = await page.locator(`pf-alert[status ="${status}"]`).first();
+ if (await alert.count() > 0) {
+ // Verify icon exists for status
+ const icon = await alert.locator('#icon');
+ expect(await icon.count()).toBe(1);
+ }
+ }
+
+ // Test variants render
+ for (const variant of ['inline', 'toast']) {
+ const alert = await page.locator(`pf-alert[variant="${variant}"]`).first();
+ if (await alert.count() > 0) {
+ // Verify basic structure
+ const container = await alert.locator('#container');
+ expect(await container.count()).toBe(1);
+ }
+ }
+ });
+});
diff --git a/elements/pf-alert/test/pf-alert.spec.ts b/elements/pf-alert/test/pf-alert.spec.ts
new file mode 100644
index 0000000000..eb2683a8eb
--- /dev/null
+++ b/elements/pf-alert/test/pf-alert.spec.ts
@@ -0,0 +1,73 @@
+import { expect, html } from '@open-wc/testing';
+import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js';
+import { PfAlert, AlertCloseEvent } from '@patternfly/elements/pf-alert/pf-alert.js';
+import { oneEvent } from '@open-wc/testing';
+
+describe('', function() {
+ describe('simply instantiating', function() {
+ let element: PfAlert;
+
+ it('imperatively instantiates', function() {
+ expect(document.createElement('pf-alert')).to.be.an.instanceof(PfAlert);
+ });
+
+ it('should upgrade', async function() {
+ element = await createFixture(html``);
+ const klass = customElements.get('pf-alert');
+ expect(element)
+ .to.be.an.instanceOf(klass)
+ .and
+ .to.be.an.instanceOf(PfAlert);
+ });
+ });
+
+ describe('attributes and properties', function() {
+ it('reflects state attribute', async function() {
+ const el = await createFixture(html``);
+ expect(el.getAttribute('state')).to.equal('success');
+ expect(el.status).to.equal('success');
+ });
+
+ it('reflects variant attribute', async function() {
+ const el = await createFixture(html``);
+ expect(el.getAttribute('variant')).to.equal('inline');
+ expect(el.variant).to.equal('inline');
+ });
+ });
+
+ describe('slots and rendering', function() {
+ it('renders header slot content', async function() {
+ const el = await createFixture(html`
+
+ Alert Title
+ Alert content
+
+ `);
+ const header = el.querySelector('[slot="header"]');
+ expect(header).to.exist;
+ expect(header?.textContent?.trim()).to.equal('Alert Title');
+ });
+
+ it('renders action buttons with correct attributes', async function() {
+ const el = await createFixture(html`
+
+
+
+ `);
+ const actionButton = el.querySelector('[slot="actions"] pf-button');
+ expect(actionButton).to.exist;
+ });
+ });
+
+ describe('events and interactions', function() {
+ it('emits close event when close button clicked', async function() {
+ const el = await createFixture(html``);
+ const closeButton = el.shadowRoot?.querySelector('#close-button');
+ setTimeout(() => closeButton?.dispatchEvent(new MouseEvent('click')));
+ const event = await oneEvent(el, 'close') as AlertCloseEvent;
+ expect(event.action).to.equal('dismiss');
+ });
+ });
+});