Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Button: add a11y documentation for icon only button #3713

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ const config = {
<kirby-icon name="edit"></kirby-icon>
Disabled with icon
</button>
<button kirby-button disabled>
<button kirby-button disabled aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>
<button kirby-button disabled [noDecoration]="true">
<button kirby-button disabled [noDecoration]="true" aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>`,
};
Expand Down
32 changes: 16 additions & 16 deletions apps/cookbook/src/app/examples/button-example/examples/icon-only.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,47 @@ import { Component } from '@angular/core';

const config = {
selector: 'cookbook-button-example-icon-only',
template: `<button kirby-button size="xs">
template: `<button kirby-button size="xs" aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>
<button kirby-button size="sm">
<button kirby-button size="sm" aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>
<button kirby-button>
<button kirby-button aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>
<button kirby-button size="lg">
<button kirby-button size="lg" aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>

<button kirby-button size="xs" attentionLevel="2">
<button kirby-button size="xs" attentionLevel="2" [showIconOnly]="true">
Search
<kirby-icon name="search"></kirby-icon>
</button>
<button kirby-button size="sm" attentionLevel="2">
<button kirby-button size="sm" attentionLevel="2" [showIconOnly]="true">
Search
<kirby-icon name="search"></kirby-icon>
</button>
<button kirby-button attentionLevel="2">
<button kirby-button attentionLevel="2" [showIconOnly]="true">
<kirby-icon name="search"></kirby-icon>
Search
</button>
<button kirby-button size="lg" attentionLevel="2">
<button kirby-button size="lg" attentionLevel="2" [showIconOnly]="true">
<kirby-icon name="search"></kirby-icon>
Search
</button>

<button kirby-button size="xs" attentionLevel="3" [showIconOnly]="true">
More settings
<button kirby-button size="xs" attentionLevel="3" aria-label="More settings">
<kirby-icon name="more"></kirby-icon>
</button>
<button kirby-button size="sm" attentionLevel="3" [showIconOnly]="true">
More settings
<button kirby-button size="sm" attentionLevel="3" aria-label="More settings">
<kirby-icon name="more"></kirby-icon>
</button>
<button kirby-button attentionLevel="3" [showIconOnly]="true">
<button kirby-button attentionLevel="3" aria-label="More settings">
<kirby-icon name="more"></kirby-icon>
More settings
</button>
<button kirby-button size="lg" attentionLevel="3" [showIconOnly]="true">
<button kirby-button size="lg" attentionLevel="3" aria-label="More settings">
<kirby-icon name="more"></kirby-icon>
More settings
</button>`,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component } from '@angular/core';

const config = {
selector: 'cookbook-button-example-undecorated',
template: `<button kirby-button [noDecoration]="true">
template: `<button kirby-button [noDecoration]="true" aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>`,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Modal, ModalController } from '@kirbydesign/designsystem';
</p>
<button kirby-button (click)="toggleFooter()">Toggle footer</button>
<kirby-modal-footer *ngIf="showFooter">
<button kirby-button attentionLevel="3" (click)="navigateToPreviousModal()">
<button kirby-button attentionLevel="3" (click)="navigateToPreviousModal()" aria-label="Back">
<kirby-icon name="arrow-back"></kirby-icon>
</button>
<button kirby-button (click)="close()">Finish</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ const config = {

<!-- Fixed Page Actions -->
<kirby-page-actions *kirbyPageActions="{fixed: true}">
<button kirby-button (click)="onMoreSelect()">
<button kirby-button (click)="onMoreSelect()" aria-label="More">
<kirby-icon name="more"></kirby-icon>
</button>
</kirby-page-actions>

<!-- Sticky Page Actions -->
<kirby-page-actions *kirbyPageActions>
<button kirby-button (click)="onCogSelect()">
<button kirby-button (click)="onCogSelect()" aria-label="Settings">
<kirby-icon name="cog"></kirby-icon>
</button>
</kirby-page-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ const config = {

<!-- Fixed Page Actions -->
<kirby-page-actions *kirbyPageActions="{fixed: true}">
<button kirby-button (click)="onMoreSelect()">
<button kirby-button (click)="onMoreSelect()" aria-label="More">
<kirby-icon name="more"></kirby-icon>
</button>
</kirby-page-actions>

<!-- Sticky Page Actions -->
<kirby-page-actions *kirbyPageActions>
<button kirby-button (click)="onCogSelect()">
<button kirby-button (click)="onCogSelect()" aria-label="Settings">
<kirby-icon name="cog"></kirby-icon>
</button>
</kirby-page-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const pageTemplate = `<kirby-page [title]="title" [tabBarBottomHidden]="!showTab
<kirby-page-footer *ngIf="showFooter">
<h3>0 selected</h3>
This is a fixed footer
<button kirby-button attentionLevel="2" class="close-footer-btn" (click)="onCloseClick()">
<button kirby-button attentionLevel="2" class="close-footer-btn" (click)="onCloseClick()" aria-label="Close">
<kirby-icon name="close"></kirby-icon>
</button>
</kirby-page-footer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const config = {
template: `<kirby-page toolbarTitle="A Fixed Title" defaultBackHref="/">
<!-- Fixed Page Actions -->
<kirby-page-actions *kirbyPageActions="{fixed: true}">
<button kirby-button (click)="onMoreSelect()">
<button kirby-button (click)="onMoreSelect()" aria-label="More">
<kirby-icon name="more"></kirby-icon>
</button>
</kirby-page-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { ActionSheetItem, ToastConfig, ToastController } from '@kirbydesign/desi
template: `
<kirby-page [title]="title | async">
<kirby-page-actions *kirbyPageActions>
<button kirby-button (click)="onCogSelect()">
<button kirby-button (click)="onCogSelect()" aria-label="Settings">
<kirby-icon name="cog"></kirby-icon>
</button>
<button kirby-button (click)="onMoreSelect()">
<button kirby-button (click)="onMoreSelect()" aria-label="More">
<kirby-icon name="more"></kirby-icon>
</button>
</kirby-page-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,45 @@ <h2>Icons</h2>

<h2>Icon only</h2>
<p>
The button can be rendered with an icon only and no text. This is useful for "close" buttons and
menu buttons.
The button can be rendered with an icon only and no visible text. This is useful for "close"
buttons and menu buttons.
</p>
<p>
To render a button with an icon only you can either include an accessible name inside the button
next to the icon and hide it visually by setting
<code>[showIconOnly]="true"</code>
or you can omit any text and set an
<code>aria-label</code>
instead. Please refer to the section on
<a href="#" (click)="scrollTo(accessibleIconButtons)">Accessible Icon Buttons</a>
below.
</p>
<p>To render a button with an icon only, include an icon within the button and omit any text.</p>
<p>
<em>
Note: If you do need to include a text for the button in the markup but still want to render
the button as icon only, set
<code>[showIconOnly]="true"</code>
. This is also useful in scenarios where the button needs to toggle between the default state
and a "collapsed" state, e.g. when used in an
Note: including a—visually hidden—label in the markup is also useful in scenarios where the
button needs to toggle between the default state and a "collapsed" state, e.g. when used in an
<a routerLink="../header">action group</a>
.
</em>
</p>
<h3 #accessibleIconButtons>Accessible Icon Buttons</h3>
<p>
When rendering a button with no visible text it's important to make the button accessible to
assistive technologies, such as screen readers.
</p>
<p>
By including a visually hidden label in the markup as mentioned above, assistive technologies
will automatically infer the name of the button from its content.
</p>
<p>
If you choose to omit the text inside the button you must set a meaningful
<code>aria-label</code>
instead.
</p>
<p>
In both cases the label should describe the action of the button, such as "Close", "Search",
"Settings" etc.
</p>

<cookbook-example-viewer [html]="iconOnlyExample.template">
<cookbook-button-example-icon-only #iconOnlyExample></cookbook-button-example-icon-only>
Expand Down Expand Up @@ -142,16 +166,16 @@ <h2>Disabled</h2>
<cookbook-button-example-disabled #disabledExample></cookbook-button-example-disabled>
</cookbook-example-viewer>

<h3>Accessible Disabled Buttons</h3>
<h3 #accessibleDisabledButtons>Accessible Disabled Buttons</h3>
<p>
The
<code>disabled</code>
attribute effectively hides the button from assistive technologies, such as screen readers, by
removing the button from the focus order of the web page.
</p>
<p>
In many scenarios it's good practice to expose the button as disabled, but still make it available for
users to find when navigating via the
In many scenarios it's good practice to expose the button as disabled, but still make it
available for users to find when navigating via the
<kbd>Tab</kbd>
key.
<br />
Expand Down Expand Up @@ -197,6 +221,17 @@ <h2>Link Button</h2>
<cookbook-button-example-link #linkExample></cookbook-button-example-link>
</cookbook-example-viewer>

<h2>Accessibility</h2>
<p>Please refer to:</p>
<ul>
<li>
<a href="#" (click)="scrollTo(accessibleIconButtons)">Accessible Icon Buttons</a>
</li>
<li>
<a href="#" (click)="scrollTo(accessibleDisabledButtons)">Accessible Disabled Buttons</a>
</li>
</ul>

<h2>API</h2>
<h3>Properties</h3>
<cookbook-api-description-properties
Expand Down
3 changes: 2 additions & 1 deletion libs/designsystem/button/src/button.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ $button-margin: utils.size('xxxs');

.content-layer {
@include utils.slotted(':not(kirby-icon)') {
display: none;
position: absolute;
scale: 0;
}
}
}
Expand Down
28 changes: 20 additions & 8 deletions libs/designsystem/button/src/button.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,11 @@ describe('ButtonComponent', () => {
expect(contentLayer.lastChild).toBe(contentLayer.querySelector('kirby-icon'));
});

it('should hide the plain text', () => {
expect(contentLayer.firstChild).toBeHidden();
it('should visually hide the plain text', () => {
expect(contentLayer.firstChild).toHaveComputedStyle({ scale: '0' });
const hiddenTextRect = (contentLayer.firstChild as HTMLElement).getBoundingClientRect();
expect(hiddenTextRect.height).toEqual(0);
expect(hiddenTextRect.width).toEqual(0);
});

it('should render as icon only', () => {
Expand Down Expand Up @@ -540,8 +543,11 @@ describe('ButtonComponent', () => {
expect(contentLayer.firstChild).toBe(contentLayer.querySelector('kirby-icon'));
});

it('should hide the plain text', () => {
expect(contentLayer.lastChild).toBeHidden();
it('should visually hide the plain text', () => {
expect(contentLayer.lastChild).toHaveComputedStyle({ scale: '0' });
const hiddenTextRect = (contentLayer.lastChild as HTMLElement).getBoundingClientRect();
expect(hiddenTextRect.height).toEqual(0);
expect(hiddenTextRect.width).toEqual(0);
});

it('should render as icon only', () => {
Expand All @@ -568,8 +574,11 @@ describe('ButtonComponent', () => {
expect(firstChild.firstChild.nodeType).toBe(Node.TEXT_NODE);
});

it('should hide the text element', () => {
expect(contentLayer.firstChild).toBeHidden();
it('should visually hide the text element', () => {
expect(contentLayer.firstChild).toHaveComputedStyle({ scale: '0' });
const hiddenTextRect = (contentLayer.firstChild as HTMLElement).getBoundingClientRect();
expect(hiddenTextRect.height).toEqual(0);
expect(hiddenTextRect.width).toEqual(0);
});

it('should render as icon only', () => {
Expand All @@ -596,8 +605,11 @@ describe('ButtonComponent', () => {
expect(lastChild.firstChild.nodeType).toBe(Node.TEXT_NODE);
});

it('should hide the text element', () => {
expect(contentLayer.lastChild).toBeHidden();
it('should visually hide the text element', () => {
expect(contentLayer.lastChild).toHaveComputedStyle({ scale: '0' });
const hiddenTextRect = (contentLayer.lastChild as HTMLElement).getBoundingClientRect();
expect(hiddenTextRect.height).toEqual(0);
expect(hiddenTextRect.width).toEqual(0);
});

it('should render as icon only', () => {
Expand Down
Loading