Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/components/visually-hidden/visually-hidden.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect, fixture, html } from '@open-wc/testing';
import { defineComponents } from '../common/definitions/defineComponents.js';
import IgcVisuallyHiddenComponent from './visually-hidden.js';

describe('VisuallyHidden', () => {
before(() => {
defineComponents(IgcVisuallyHiddenComponent);
});

it('passes the a11y audit', async () => {
const el = await fixture<IgcVisuallyHiddenComponent>(
html`<igc-visually-hidden>Hidden text</igc-visually-hidden>`
);

await expect(el).to.be.accessible();
await expect(el).shadowDom.to.be.accessible();
});

it('renders slotted content', async () => {
const el = await fixture<IgcVisuallyHiddenComponent>(
html`<igc-visually-hidden>Screen reader text</igc-visually-hidden>`
);

expect(el).dom.to.have.text('Screen reader text');
});

it('is hidden from visual layout when not focused', async () => {
const el = await fixture<IgcVisuallyHiddenComponent>(
html`<igc-visually-hidden>Hidden</igc-visually-hidden>`
);

const styles = getComputedStyle(el);
expect(styles.position).to.equal('absolute');
expect(styles.width).to.equal('1px');
expect(styles.height).to.equal('1px');
});

it('becomes visible when focus is within', async () => {
const el = await fixture<IgcVisuallyHiddenComponent>(
html`<igc-visually-hidden><a href="#">Skip</a></igc-visually-hidden>`
);

const link = el.querySelector('a')!;
link.focus();

// When focused, :host(:not(:focus-within)) no longer matches,
// so the element is no longer clipped to 1px
const styles = getComputedStyle(el);
expect(styles.position).to.not.equal('absolute');
});

it('renders a slot element in shadow DOM', async () => {
const el = await fixture<IgcVisuallyHiddenComponent>(
html`<igc-visually-hidden>text</igc-visually-hidden>`
);

expect(el).shadowDom.to.equal('<slot></slot>');
});
});
74 changes: 74 additions & 0 deletions src/components/visually-hidden/visually-hidden.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { css, html, LitElement } from 'lit';
import { registerComponent } from '../common/definitions/register.js';

/* blazorSuppress */
/**
* A utility component that visually hides its content while keeping it
* accessible to screen readers and other assistive technologies.
*
* The content is only visible when it receives focus, making it ideal for
* skip navigation links and other focus-based accessibility patterns.
*
* @element igc-visually-hidden
*
* @slot - Default slot for the visually hidden content.
*
* @example
* ```html
* <!-- Hide a label visually while keeping it accessible -->
* <igc-visually-hidden>
* <label for="search">Search</label>
* </igc-visually-hidden>
* <input id="search" type="search" placeholder="Search..." />
* ```
*
* @example
* ```html
* <!-- Skip navigation link that becomes visible on focus -->
* <igc-visually-hidden>
* <a href="#main-content">Skip to main content</a>
* </igc-visually-hidden>
* ```
*
* @example
* ```html
* <!-- Provide additional context for icon-only buttons -->
* <button>
* <igc-icon name="close"></igc-icon>
* <igc-visually-hidden>Close dialog</igc-visually-hidden>
* </button>
* ```
*/
export default class IgcVisuallyHiddenComponent extends LitElement {
public static readonly tagName = 'igc-visually-hidden';

public static override styles = css`
:host(:not(:focus-within)) {
position: absolute;
width: 1px;
height: 1px;
border: 0;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
}
`;

/* blazorSuppress */
public static register(): void {
registerComponent(IgcVisuallyHiddenComponent);
}

protected override render() {
return html`<slot></slot>`;
}
}

declare global {
interface HTMLElementTagNameMap {
'igc-visually-hidden': IgcVisuallyHiddenComponent;
}
}
Loading