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

✨ Site accessibility improvements #4131

Merged
merged 11 commits into from
Dec 4, 2024
13 changes: 12 additions & 1 deletion packages/@ourworldindata/components/src/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ type ButtonCommonProps = {
type WithHrefProps = {
href: string
onClick?: never
ariaLabel?: never
type?: never
}

type WithOnClickProps = {
onClick?: () => void
href?: never
ariaLabel: string
type?: "button" | "submit"
}

export type ButtonProps =
Expand All @@ -31,6 +35,8 @@ export const Button = ({
href,
onClick,
text,
ariaLabel,
type = "button",
icon = faArrowRight,
}: ButtonProps) => {
const classes = cx("owid-btn", `owid-btn--${theme}`, className)
Expand All @@ -44,7 +50,12 @@ export const Button = ({
}

return (
<button className={classes} onClick={onClick}>
<button
aria-label={ariaLabel}
type={type}
className={classes}
onClick={onClick}
>
{text} {icon && <FontAwesomeIcon icon={icon} />}
</button>
)
Expand Down
13 changes: 13 additions & 0 deletions packages/@ourworldindata/components/src/styles/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@
}
}

@mixin owid-link-40 {
color: $blue-40;
text-decoration: underline;
text-underline-offset: 4px;

&:visited {
color: $blue-30;
}
&:hover {
text-decoration: none;
}
}

@mixin cancel-link-styles {
color: $blue-90;
text-decoration: none;
Expand Down
1 change: 1 addition & 0 deletions packages/@ourworldindata/grapher/src/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ export class Footer<
<a
className="learn-more-about-data"
data-track-note="chart_click_sources"
tabIndex={0}
onClick={action((e) => {
e.stopPropagation()

Expand Down
12 changes: 12 additions & 0 deletions packages/@ourworldindata/utils/src/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2000,3 +2000,15 @@ export function getPaginationPageNumbers(

return pageNumbers
}

/**
* Convert 0 to "0th", 1 to "1st", etc.
* */
export function getOrdinalNumberString(n: number): string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a big deal, but consider using Intl.PluralRules, which would make this more readable (IMHO) and standardized.

Copy link
Contributor

Choose a reason for hiding this comment

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

@rakyi Just a drive by comment to say thanks for mentioning this, I didn't know about it and this seems pretty neat. Maybe post about it on slack in developers or tools-and-workflows?

const suffix = ["th", "st", "nd", "rd"]
const modulusValue = n % 100
return (
n +
(suffix[(modulusValue - 20) % 10] || suffix[modulusValue] || suffix[0])
)
}
1 change: 1 addition & 0 deletions packages/@ourworldindata/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export {
formatInlineList,
lazy,
getParentVariableIdFromChartConfig,
getOrdinalNumberString,
} from "./Util.js"

export {
Expand Down
5 changes: 3 additions & 2 deletions site/DataCatalog/DataCatalogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SiteHeader } from "../SiteHeader.js"
import { SiteFooter } from "../SiteFooter.js"
import { SiteFooterContext, TagGraphRoot } from "@ourworldindata/utils"
import { DataCatalogInstantSearchWrapper } from "./DataCatalog.js"
import { Html } from "../Html.js"

declare global {
interface Window {
Expand All @@ -18,7 +19,7 @@ export const DataCatalogPage = (props: {
const { baseUrl, tagGraph } = props

return (
<html>
<Html>
<Head
canonicalUrl={`${baseUrl}/charts`}
pageTitle="Data Catalog"
Expand Down Expand Up @@ -47,6 +48,6 @@ export const DataCatalogPage = (props: {
hideDonationFlag
/>
</body>
</html>
</Html>
)
}
8 changes: 7 additions & 1 deletion site/Html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@ import React, { HtmlHTMLAttributes } from "react"
* See the <script> tag in Head.tsx / NoJSDetector.tsx for the client-side JavaScript that removes this class.
*/
export const Html = (props: HtmlHTMLAttributes<Element>) => {
return <html {...props} className={cx("js-disabled", props.className)} />
return (
<html
{...props}
lang="en"
className={cx("js-disabled", props.className)}
/>
)
}
16 changes: 9 additions & 7 deletions site/SiteFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,15 @@ export const SiteFooter = (props: SiteFooterProps) => (
Please consult our full{" "}
<a href="/about#legal">legal disclaimer</a>.
</p>
<p>
<p className="legal--last-paragraph">
<span>
Our World In Data is a project of the{" "}
<a href="https://global-change-data-lab.org/">
Global Change Data Lab
</a>
, a registered charity in England and Wales
(Charity Number 1186433).
</span>
<a
href="https://global-change-data-lab.org/"
className="partner-logo gcdl-logo"
Expand All @@ -260,12 +268,6 @@ export const SiteFooter = (props: SiteFooterProps) => (
/>
</picture>
</a>
Our World In Data is a project of the{" "}
<a href="https://global-change-data-lab.org/">
Global Change Data Lab
</a>
, a registered charity in England and Wales
(Charity Number 1186433).
</p>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion site/blocks/AllChartsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ export const AllChartsListItem = ({
href={`${BAKED_BASE_URL}/grapher/${chart.slug}`}
onClick={onClick}
>
<GrapherImage slug={chart.slug} noFormatting />
<GrapherImage
slug={chart.slug}
alt={`A thumbnail of the "${chart.title}" chart`}
noFormatting
/>
<span>{chart.title}</span>
</a>
{chart.variantName ? (
Expand Down
4 changes: 4 additions & 0 deletions site/blocks/ExpandableParagraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export const ExpandableParagraph = (
<div className={cx("expandable-paragraph", className)}>
<div
style={contentStyles}
// inert isn't supported in react@17
// prevents focus on elements that are not visible
// ideally would only apply to elements below the fold but that's hard
{...{ inert: isClosed ? "true" : undefined }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this really what we want? What problem do we solve? It also prevents users from selecting text within the element, and the text will be excluded from browser search, which seems too harsh.

Copy link
Member Author

Choose a reason for hiding this comment

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

The issue I was trying to address here is that you can tab through links that are hidden, which renders poorly (see the video.)

I tried setting up a prop that would set tabIndex="-1" on any nested span-link elements but couldn't get it to work in the small amount of time I was budgeting for this PR so I went with this simpler solution instead.

I'm open to changing this though, if you think this change is an overall downgrade 🙂

tabbing.mov

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, I'm not sure what's better. Let's go with what you did and revert/rework later, if necessary.

ref={containerRef}
// Either pass children or dangerouslySetInnerHTML
{...propsWithoutStyles}
Expand Down
3 changes: 3 additions & 0 deletions site/css/cookie-notice.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@

.cookie-notice__text {
margin: 0;
a {
@include owid-link-90;
}

@include lg-up {
text-align: left;
Expand Down
29 changes: 19 additions & 10 deletions site/css/footer.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.site-footer {
background-color: $oxford-blue;
padding: 3rem 0;
color: $blue-50;
color: scale-color($blue-50, $lightness: 5%);

@include sm-only {
text-align: center;
Expand All @@ -14,20 +14,15 @@
}

a {
color: inherit;
transition: color 150ms ease;

&:hover {
color: $blue-30;
}
@include owid-link-40;
}

ul {
list-style-type: none;
margin-bottom: 1rem;

a {
display: block;
display: inline-block;
text-decoration: none;

@include sm-only {
Expand All @@ -47,12 +42,26 @@

.legal {
font-size: 0.875rem;
color: $blue-60;
color: scale-color($blue-50, $lightness: 5%);
text-align: left;

p {
margin: 0 0 1rem;
}

.legal--last-paragraph {
display: flex;
span {
flex: 7;
}
.partner-logo {
flex: 1;
text-align: right;
img {
max-width: 48px;
}
}
}
}

.partner-logo {
Expand All @@ -63,7 +72,7 @@
opacity 150ms ease,
filter 150ms ease;
filter: grayscale(1);
opacity: 0.5;
opacity: 0.9;

&:hover {
filter: grayscale(0);
Expand Down
3 changes: 0 additions & 3 deletions site/css/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@ $checkbox-check-height: 4px;
&:hover {
opacity: 0.85;
}
&:focus {
outline: 4px solid $highlight-color;
}
}

%owid-button {
Expand Down
6 changes: 5 additions & 1 deletion site/gdocs/components/AllCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ export function AllCharts(props: AllChartsProps) {
id={ALL_CHARTS_ID}
>
{heading}
<a className="deep-link" href={`#${ALL_CHARTS_ID}`} />
<a
className="deep-link"
aria-labelledby={ALL_CHARTS_ID}
href={`#${ALL_CHARTS_ID}`}
/>
</h1>
<RelatedCharts
showKeyChartsOnly={true}
Expand Down
6 changes: 5 additions & 1 deletion site/gdocs/components/ArticleBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,11 @@ export default function ArticleBlock({
) : null}
{renderSpans(text, shouldRenderLinks)}
{shouldRenderLinks && (
<a className="deep-link" href={`#${id}`} />
<a
className="deep-link"
aria-labelledby={id}
href={`#${id}`}
/>
)}
</h3>
)
Expand Down
2 changes: 2 additions & 0 deletions site/gdocs/components/DataInsightsNewsletter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ export default function DataInsightsNewsletter({
/>
<input type="hidden" name="group[85302][16]" value="" />
<Button
ariaLabel="Subscribe to the Daily Data Insights newsletter"
theme="solid-vermillion"
text="Subscribe"
type="submit"
icon={null}
onClick={() =>
analytics.logSiteClick(
Expand Down
4 changes: 4 additions & 0 deletions site/gdocs/components/KeyInsights.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ $slide-content-height: $grapher-height;
color: $blue-90;
letter-spacing: -0.2px;
}
&:focus {
border-color: $gray-50;
outline: none;
}
}

.slides {
Expand Down
6 changes: 5 additions & 1 deletion site/gdocs/components/KeyInsights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,11 @@ export const KeyInsights = ({
id={KEY_INSIGHTS_ID}
>
{heading}
<a className="deep-link" href={`#${KEY_INSIGHTS_ID}`} />
<a
className="deep-link"
aria-labelledby={KEY_INSIGHTS_ID}
href={`#${KEY_INSIGHTS_ID}`}
/>
</h1>
<div className={KEY_INSIGHTS_CLASS_NAME}>
<div>
Expand Down
10 changes: 8 additions & 2 deletions site/gdocs/components/LatestDataInsights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
EnrichedBlockImage,
OwidEnrichedGdocBlock,
LatestDataInsight,
getOrdinalNumberString,
} from "@ourworldindata/utils"
import { dataInsightIndexToIdMap } from "../pages/DataInsight.js"
import Image from "./Image.js"
Expand Down Expand Up @@ -111,6 +112,7 @@ export default function LatestDataInsights({
</div>
{canScrollPrev && (
<Button
ariaLabel="Scroll to the previous data insight card"
className="latest-data-insights__control-button latest-data-insights__control-button--prev"
theme="solid-blue"
onClick={scrollPrev}
Expand All @@ -120,6 +122,7 @@ export default function LatestDataInsights({
)}
{canScrollNext && (
<Button
ariaLabel="Scroll to the next data insight card"
className="latest-data-insights__control-button latest-data-insights__control-button--next"
theme="solid-blue"
onClick={scrollNext}
Expand All @@ -131,6 +134,7 @@ export default function LatestDataInsights({
{scrollSnaps.map((_, index) => (
<DotButton
key={index}
index={index}
onClick={() => onDotButtonClick(index)}
className={cx("latest-data-insights__control-dot", {
"latest-data-insights__control-dot--selected":
Expand Down Expand Up @@ -251,10 +255,12 @@ function useDotButton(emblaApi: EmblaCarouselType | undefined): {

function DotButton({
children,
index,
...restProps
}: ComponentPropsWithRef<"button">) {
}: ComponentPropsWithRef<"button"> & { index: number }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I was meaning to change this to a div instead of a button and remove the interactivity. The buttons are too small for touch navigation — it was flagged as such by some other web test tool I used. Should we do that instead?

const label = `Navigate to the ${getOrdinalNumberString(index + 1)} data insight card`
return (
<button type="button" {...restProps}>
<button aria-label={label} {...restProps}>
{children}
</button>
)
Expand Down
2 changes: 1 addition & 1 deletion site/gdocs/components/PillRow.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
padding: 8px 0;
}
p {
color: $blue-60;
color: $blue-65;
margin: 0 9px 0 0;
line-height: 32px;
white-space: nowrap;
Expand Down
Loading