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

TCCP: Tooltip accessibility improvements #8506

Merged
merged 6 commits into from
Jul 15, 2024
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
24 changes: 17 additions & 7 deletions cfgov/tccp/jinja2/tccp/includes/card_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,23 @@
<div class="o-card-group">
<div class="o-card-group__cards">
{%- for card in cards %}
{%- set card_purchase_apr = apr_range(
card.purchase_apr_for_tier_min,
card.purchase_apr_for_tier_max,
asterisk=true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the asterisk read by a screen reader when this is used in the aria-label? If it sounds weird, I was going to suggest using an asterisk=false version in the aria-label. But if it's also read awkwardly in the card content, maybe we can update the apr_range macro to make the asterisk aria-hidden everywhere so it's never read.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point, the asterisk does not read well either on the results page or the card detail pages. I'm thinking we should either migrate to a footnote format (e.g. [1]) that links to the explanatory text or we can try replacing the asterisks with the full explanatory text for screenreaders only. I'll experiment and open another PR.

) -%}
{%- set card_account_fee = currency(
card.annual_fee_estimated,
default=('See details' if card.annual_fee_estimated is none and card.periodic_fee_type else '$0')
) -%}
{%- set show_more = loop.index > 11 and ordering_by != 'product_name' -%}
<article class="m-card m-card--tabular{% if show_more %} u-show-more{% endif %}">
<a href="{{ card.url }}" data-js-hook="behavior_ignore-link-targets" data-ignore-link-targets="[data-tooltip]">
<a
href="{{ card.url }}"
data-js-hook="behavior_ignore-link-targets"
data-ignore-link-targets="[data-tooltip]"
aria-label="{{ card.product_name }} from {{ card.institution_name }} with {{ card_purchase_apr }} purchase APR, {{ card_account_fee }} account fee, {{ card_rewards(card) }} card rewards{%- if card.requirements_for_opening or card.issued_by_credit_union -%}, eligibility requirements{%- endif -%}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add the data published date in the description so it reads something like "9.99% purchase APR as of December 31, 2023"? That might be overkill and make for a too long summary, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally including the date in the aria label but it felt very repetitive and obnoxious to repeat "as of December 31, 2023" over and over again as the user tabs through the cards. I figure the card detail page has all the juicy details so it's okay to keep the card summaries concise.

>
<div class="m-card__heading-group">
<h2 class="h3 m-card__heading">{{ card.institution_name }}</h2>
<p class="m-card__subtitle">{{ card.product_name }}</p>
Expand Down Expand Up @@ -95,17 +109,13 @@ <h2 class="h3 m-card__heading">{{ card.institution_name }}</h2>
</div>
</dt>
<dd>
<strong>{{ apr_range(
card.purchase_apr_for_tier_min,
card.purchase_apr_for_tier_max,
asterisk=true
) }}</strong>
<strong>{{ card_purchase_apr }}</strong>
</dd>
</div>
<div class="m-data-spec m-data-spec--fee">
<dt><strong>Account fee</strong></dt>
<dd>
<strong>{{ currency(card.annual_fee_estimated, default=('See details' if card.annual_fee_estimated is none and card.periodic_fee_type else '$0')) }}</strong>
<strong>{{ card_account_fee }}</strong>
</dd>
</div>
<div class="m-data-spec m-data-spec--transfer">
Expand Down
5 changes: 5 additions & 0 deletions cfgov/unprocessed/apps/tccp/css/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@
padding: unit((20px / @base-font-size-px), rem);
background-color: var(--teal-10);

a {
border-color: var(--pacific-mid-dark);
color: var(--pacific-mid-dark);
}

p {
max-width: 100%;
}
Expand Down
50 changes: 4 additions & 46 deletions cfgov/unprocessed/apps/tccp/css/tooltip.less
Original file line number Diff line number Diff line change
@@ -1,58 +1,16 @@
@import (css) '../node_modules/tippy.js/dist/tippy.css';
@import (css) '../node_modules/tippy.js/dist/border.css';

// Custom theme, see https://kabbouchi.github.io/tippyjs-v4-docs/themes/
// Custom theme, see https://atomiks.github.io/tippyjs/v6/themes/
.tippy-box[data-theme='cfpb'] {
background-color: var(--gray-5);
background-clip: padding-box;
border: 1px solid var(--gray-40);
border-radius: 0;
color: var(--black);
padding: unit((15px / @base-font-size-px), rem);

&[data-placement^='top'] > .tippy-arrow {
&::before {
border-top-color: var(--white);
}
&::after {
border-top-color: rgba(0, 8, 16, 20%);
border-width: 7px 7px 0;
top: 17px;
left: 1px;
}
}

&[data-placement^='top'] > .tippy-svg-arrow > svg {
top: 16px;
}

&[data-placement^='top'] > .tippy-svg-arrow::after {
top: 17px;
}

> .tippy-backdrop {
background-color: var(--white);
}

> .tippy-arrow::after,
> .tippy-svg-arrow::after {
content: '';
position: absolute;
z-index: -1;
}

> .tippy-arrow::after {
border-color: transparent;
border-style: solid;
}

> .tippy-svg-arrow {
fill: var(--white);
&::after {
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=');
background-size: 16px 6px;
width: 16px;
height: 6px;
}
.tippy-arrow {
color: var(--gray-5);
}

.tippy-heading {
Expand Down
22 changes: 22 additions & 0 deletions cfgov/unprocessed/apps/tccp/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ function initializeTooltips() {
container.appendChild(node);
return container;
},
// See https://atomiks.github.io/tippyjs/v6/plugins/
plugins: [
{
name: 'hideOnEsc',
defaultValue: true,
fn({ hide }) {
function onKeyDown(event) {
if (event.keyCode === 27) {
hide();
}
}
return {
onShow() {
document.addEventListener('keydown', onKeyDown);
},
onHide() {
document.removeEventListener('keydown', onKeyDown);
},
};
},
},
],
});
}

Expand Down
Binary file added npm-packages-offline-cache/axe-core-4.9.1.tgz
Binary file not shown.
Binary file added npm-packages-offline-cache/cypress-axe-1.5.0.tgz
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
"@cfpb/cfpb-typography": "1.3.2",
"@cypress/skip-test": "2.6.1",
"autoprefixer": "10.4.19",
"axe-core": "4.9.1",
"cfpb-chart-builder": "6.5.0",
"cypress-axe": "1.5.0",
"esbuild": "0.23.0",
"fancy-log": "2.0.0",
"highcharts": "7.2.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ export class ExploreCreditCards {
selectCheckboxFilter(name, value) {
cy.get(`input[name=${name}]`).check(value, { force: true });
}

checkA11y() {
cy.injectAxe();
cy.checkA11y();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('Explore credit cards landing page', () => {
exploreCards.openFilterExpandable();

cy.get('#id_rewards input').should('be.checked');
exploreCards.checkA11y();
});

it("should show an error message if a location isn't selected", () => {
Expand All @@ -24,6 +25,7 @@ describe('Explore credit cards landing page', () => {
exploreCards.clickSubmitButton();

cy.get('.a-form-alert__text').should('be.visible');
exploreCards.checkA11y();

exploreCards.selectLocation('NY');

Expand All @@ -45,13 +47,23 @@ describe('Explore credit cards results page', () => {
it('should not follow card links when tooltips are clicked', () => {
exploreCards.openResultsPage();

cy.get('.m-card--tabular [data-tooltip]').first().click();
cy.get('.m-card--tabular [data-tooltip]').first().trigger('mouseenter');

cy.get('h1').contains('Explore credit cards').should('exist');
cy.get('h2')
.contains('Purchase interest rate and fees')
.should('not.exist');
});
it('should close tooltips when escape key is pressed', () => {
exploreCards.openResultsPage();

cy.get('.m-card--tabular [data-tooltip]').first().trigger('mouseenter');
cy.get('div.tippy-heading').should('be.visible');

cy.get('.m-card--tabular [data-tooltip]').first().type('{esc}');
cy.wait(1000);
cy.get('div.tippy-heading').should('not.exist');
});
it('should not follow card links when tooltips are open', () => {
exploreCards.openResultsPage();

Expand Down Expand Up @@ -92,6 +104,7 @@ describe('Explore credit cards results page', () => {
exploreCards.selectOrdering('Purchase APR');
cy.get('.htmx-container.htmx-request').should('not.exist');
cy.get('#u-show-more-fade').should('be.visible');
exploreCards.checkA11y();

exploreCards.selectOrdering('Card name');
cy.get('.htmx-container.htmx-request').should('not.exist');
Expand Down Expand Up @@ -159,6 +172,7 @@ describe('Explore credit card details page', () => {
.and('contain', 'credit_tier=Credit+score+of+720+or+greater')
.and('contain', 'location=NY')
.and('contain', 'situations=Earn+rewards');
exploreCards.checkA11y();
});
it('should have a breadcrumb to full list if the user never filtered', () => {
exploreCards.openResultsPage();
Expand Down
3 changes: 3 additions & 0 deletions test/cypress/support/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ require('@cypress/skip-test/support');

// Fail Cypress tests fast on the first failure.
import 'cypress-fail-fast';

// Initialize a11y plugin https://github.com/component-driven/cypress-axe
import 'cypress-axe';
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,11 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3"
integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==

[email protected]:
version "4.9.1"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae"
integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==

axe-core@=4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
Expand Down Expand Up @@ -1952,6 +1957,11 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"

[email protected]:
version "1.5.0"
resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.5.0.tgz#95082734583da77b51ce9b7784e14a442016c7a1"
integrity sha512-Hy/owCjfj+25KMsecvDgo4fC/781ccL+e8p+UUYoadGVM2ogZF9XIKbiM6KI8Y3cEaSreymdD6ZzccbI2bY0lQ==

[email protected]:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cypress-fail-fast/-/cypress-fail-fast-7.1.0.tgz#36e28e39fffaacf852b4866c8459e5274eb8326d"
Expand Down
Loading