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

feat(components/atom/icon): icon a11y #2830

Merged
merged 3 commits into from
Feb 7, 2025
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
117 changes: 117 additions & 0 deletions components/atom/icon/demo/articles/ArticleA11y.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import PropTypes from 'prop-types'

import {
Anchor,
Article,
Box,
H2,
H3,
H4,
Code,
ListItem,
Paragraph,
Strong,
UnorderedList
} from '@s-ui/documentation-library'

const ArticleA11y = ({className}) => {
return (
<Article className={className}>
<H2>
Accessibility –{' '}
<Anchor href="https://github.com/SUI-Components/sui-components/blob/master/contributor-docs/Accessibility%20Standards.md">
<Strong>Guidelines</Strong>
</Anchor>
</H2>
<Box style={{backgroundColor: 'color-mix(in srgb, #00FF00 10%, transparent)'}}>
<Paragraph>
✅ This component has been successfully tested for{' '}
<Strong>WCAG 2.0 levels A and AA, WCAG 2.1 levels A and AA</Strong> and for common accessibility best
practices.
</Paragraph>
</Box>
<Paragraph>
Icons need to be understandable and user-friendly for everyone, including those with disabilities.
</Paragraph>
<H3>Key elements of accessible icon design</H3>
<UnorderedList>
<ListItem>
<Strong>Clear visibility</Strong>: The goal is to ensure that every user, regardless of their visual
abilities, can <Strong>easily identify and understand</Strong> the icons. This involves choosing colours with
sufficient contrast to ensure that icons are distinguishable for users with colour vision deficiencies or
visual impairments and maintain clarity in various screen resolutions and sizes
</ListItem>
<ListItem>
<Strong>Universal recognisability</Strong>: An icon’s design should{' '}
<Strong>communicate clearly and intuitively</Strong> its function, reducing the cognitive load on users. Opt
for symbols and imagery that are widely understood <Strong>across different cultures and demographics</Strong>
.
</ListItem>
<ListItem>
<Strong>Appropriate size</Strong>: Icons need to be large enough to be <Strong>easily interacted</Strong> with
yet balanced enough <Strong>not to overwhelm</Strong> the interface. This entails considering touch targets
for users on mobile devices to make sure that icons can be tapped without error. This also encompasses
accommodating users with motor impairments who might find interacting with smaller icons challenging.
</ListItem>
<ListItem>
<Strong>Discoverability with a keyboard, mouse, and screen reader</Strong>: Icon buttons must be navigable
using a variety of tools like keyboards, mice, and especially screen readers for users with visual
impairments. This involves proper coding practices, such as including alt text and{' '}
<Anchor href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">
ARIA (Accessible Rich Internet Applications)
</Anchor>{' '}
labels, to ensure that icons are seen and understood in the way they are intended.
</ListItem>
</UnorderedList>
<H3>Summary of rules for proper A11y criteria on Icons:</H3>
<H4>1.- Is this image purely decorative?</H4>
<Article style={{padding: '0 16px'}}>
<Paragraph>
Some examples of icons are <Strong>not relevant for the content</Strong>, and they are just decorative. In
this case, you should use the <Code>aria-hidden="true"</Code> attribute. At the same time, you{' '}
<Strong>
should never add a <Code>label</Code> or an <Code>alt</Code>
</Strong>{' '}
to the icon.
</Paragraph>
<Paragraph>
Ask yourself, if this image wasn’t present on the page, what would the user miss out on? If the answer is
purely that it wouldn’t look as nice, then it sounds like{' '}
<Strong>you don’t need alternative text and the aria-hidden has to be added</Strong>.
</Paragraph>
<Paragraph>Examples:</Paragraph>
<UnorderedList>
<ListItem>Images used for layout purposes only</ListItem>
<ListItem>Images used as bullet points</ListItem>
<ListItem>Images where the surrounding text is descriptive enough already</ListItem>
</UnorderedList>
</Article>
<H4>2.- Does this image have a function, or is it purely content?</H4>
<Article style={{padding: '0 16px'}}>
<Paragraph>
If the icon is associated with an interactive element, such as a <Code>link</Code> or a <Code>Button</Code>,
then we can add an <Code>‘aria-label’</Code> to the interactive element, while hiding the icon itself as in
Step 1.
</Paragraph>
</Article>
<H4>3.- What content would the user miss out on if the image was missing?</H4>
<Article style={{padding: '0 16px'}}>
<Paragraph>
As we cannot add alternative text to the icon itself, what we can do is add some actual text, but display it
in a way to make it only visible to assistive technology users. This can be done using CSS and displaying the
text off screen. As before, we will add <Code>‘aria-hidden’</Code> to the icon itself.
</Paragraph>
</Article>
<Paragraph>
The final question is the most difficult one to answer, but by taking a little time over this, you can improve
the experience for all of all users as well as SEO!
</Paragraph>
</Article>
)
}

ArticleA11y.propTypes = {
className: PropTypes.string
}

export default ArticleA11y
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import {useState} from 'react'

import PropTypes from 'prop-types'

import {AntDesignIcon, Article, H2, Paragraph, RadioButton, RadioButtonGroup} from '@s-ui/documentation-library'

import AtomIcon, {ATOM_ICON_SIZES} from '../src/index.js'
import {CLASS_SECTION, ICONS} from './settings.js'
import AtomIcon, {ATOM_ICON_SIZES} from '../../src/index.js'
import {ICONS} from '../settings.js'

const ColorInheritanceDemo = () => {
const ArticleColorInheritance = ({className}) => {
const COLOR_EXAMPLES = ['#0099ff', 'rgb(36, 211, 212)', 'brown']
const [selectedColor, setColor] = useState()
return (
<Article className={CLASS_SECTION}>
<Article className={className}>
<H2>Color inheritance</H2>
<Paragraph>
currentColor value for color prop is the default value and will inherit the color from the inmediate nearest
currentColor value for color prop is the default value and will inherit the color from the immediate nearest
parent value of color property in CSS. This way you could safely make your icon get a different color with
endless posibilities to match your designs without having to care about variables on the SUI components.
endless possibilities to match your designs without having to care about variables on the SUI components.
</Paragraph>
–––
<br />
Expand All @@ -27,12 +29,16 @@ const ColorInheritanceDemo = () => {
<Paragraph className="DemoAtomIcon-colorExample" style={{color: selectedColor}}>
The icon{' '}
<AtomIcon color="currentColor" size={ATOM_ICON_SIZES.medium}>
<AntDesignIcon icon={Object.values(ICONS)[1]} style={{color: 'currentColor'}} />
<AntDesignIcon icon={Object.values(ICONS)[1].name} style={{color: 'currentColor'}} />
</AtomIcon>{' '}
inherits the color of this text {selectedColor}
</Paragraph>
</Article>
)
}

export default ColorInheritanceDemo
ArticleColorInheritance.propTypes = {
className: PropTypes.string
}

export default ArticleColorInheritance
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {Fragment, useState} from 'react'

import PropTypes from 'prop-types'

import {
AntDesignIcon,
Article,
Expand All @@ -13,13 +15,13 @@ import {
RadioButtonGroup
} from '@s-ui/documentation-library'

import AtomIcon, {ATOM_ICON_COLORS, ATOM_ICON_SIZES} from '../src/index.js'
import {CLASS_SECTION, flexCenteredStyle, ICONS} from './settings.js'
import AtomIcon, {ATOM_ICON_COLORS, ATOM_ICON_SIZES} from '../../src/index.js'
import {flexCenteredStyle, ICONS} from '../settings.js'

const ColorsAndSizesDemo = () => {
const ArticleColorsAndSizes = ({className}) => {
const [selectedIcon, setIcon] = useState(Object.values(ICONS)[0])
return (
<Article className={CLASS_SECTION}>
<Article className={className}>
<H2>Colors & Sizes</H2>
<Paragraph>
Icons can change its inner colors using the <Code>color</Code> prop. The inner svg elements fill inherit by
Expand All @@ -33,13 +35,14 @@ const ColorsAndSizesDemo = () => {
<br />
<br />
<RadioButtonGroup onChange={(event, value) => setIcon(value)} value={selectedIcon}>
{Object.values(ICONS).map((iconName, index) => (
{Object.values(ICONS).map(({name, label}, index) => (
<RadioButton
key={index}
value={iconName}
value={{name, label}}
aria-label={label}
label={
<AtomIcon>
<AntDesignIcon icon={iconName} style={{color: 'currentColor'}} />
<AntDesignIcon icon={name} style={{color: 'currentColor'}} />
</AtomIcon>
}
/>
Expand All @@ -63,7 +66,7 @@ const ColorsAndSizesDemo = () => {
{Object.values(ATOM_ICON_COLORS).map((iconColor, indexColor) => (
<Cell key={`${indexSize}-${indexColor}`} style={{...flexCenteredStyle, minHeight: 32}}>
<AtomIcon color={iconColor} size={iconSize}>
<AntDesignIcon icon={selectedIcon} style={{color: 'currentColor'}} />
<AntDesignIcon icon={selectedIcon.name} style={{color: 'currentColor'}} />
</AtomIcon>
</Cell>
))}
Expand All @@ -78,4 +81,8 @@ const ColorsAndSizesDemo = () => {
)
}

export default ColorsAndSizesDemo
ArticleColorsAndSizes.propTypes = {
className: PropTypes.node
}

export default ArticleColorsAndSizes
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import PropTypes from 'prop-types'

import {AntDesignIcon, Article, Cell, Code, Grid, H2, Paragraph, Strong} from '@s-ui/documentation-library'

import AtomIcon from '../src/index.js'
import {CLASS_SECTION, ICONS} from './settings.js'
import AtomIcon from '../../src/index.js'
import {ICONS} from '../settings.js'

const LazyDemo = () => (
<Article className={CLASS_SECTION}>
const ArticleLazy = ({className}) => (
<Article className={className}>
<H2>Lazy Icons</H2>
<Paragraph>
By default, icons will be rendered <Strong>eagerly</Strong>. That means that they will be rendered on the server
Expand All @@ -24,4 +26,8 @@ const LazyDemo = () => (
</Article>
)

export default LazyDemo
ArticleLazy.propTypes = {
className: PropTypes.node
}

export default ArticleLazy
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import {useState} from 'react'

import PropTypes from 'prop-types'

import {AntDesignIcon, Article, Code, H2, Paragraph, RadioButton, RadioButtonGroup} from '@s-ui/documentation-library'

import AtomIcon, {ATOM_ICON_SIZES} from '../src/index.js'
import {CLASS_SECTION, ICONS} from './settings.js'
import AtomIcon, {ATOM_ICON_SIZES} from '../../src/index.js'
import {ICONS} from '../settings.js'

const PolymorphicDemo = () => {
const ArticlePolymorphic = ({className}) => {
const ELEMENTS = [undefined, 'button', 'i', 'div']
const [selectedElement, setElement] = useState()
return (
<Article className={CLASS_SECTION}>
<Article className={className}>
<H2>Polymorphism</H2>
<Paragraph>
You can select the element tag using the <Code>as</Code> prop. By default it is an span.
Expand All @@ -24,11 +26,15 @@ const PolymorphicDemo = () => {
</RadioButtonGroup>
<div style={{display: 'flex'}}>
<AtomIcon as={selectedElement} color="currentColor" size={ATOM_ICON_SIZES.medium}>
<AntDesignIcon icon={Object.values(ICONS)[1]} style={{color: 'currentColor'}} />
<AntDesignIcon icon={Object.values(ICONS)[1].name} style={{color: 'currentColor'}} />
</AtomIcon>
</div>
</Article>
)
}

export default PolymorphicDemo
ArticlePolymorphic.propTypes = {
className: PropTypes.node
}

export default ArticlePolymorphic
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import PropTypes from 'prop-types'

import {Article, Code, H2, Paragraph} from '@s-ui/documentation-library'
import AtomButton from '@s-ui/react-atom-button'

import AtomIcon from '../src/index.js'
import {CLASS_SECTION} from './settings.js'
import AtomIcon from '../../src/index.js'

const SpanDemo = () => (
<Article className={CLASS_SECTION}>
const ArticleSpan = ({className}) => (
<Article className={className}>
<H2>Icons wrapped with span</H2>
<Paragraph>
Some icons could be wrapped on a <Code>span</Code> tag so they could be rendered by using
Expand Down Expand Up @@ -40,4 +41,8 @@ const SpanDemo = () => (
</Article>
)

export default SpanDemo
ArticleSpan.propTypes = {
className: PropTypes.node
}

export default ArticleSpan
45 changes: 34 additions & 11 deletions components/atom/icon/demo/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,54 @@
/* eslint-disable react/prop-types, no-unused-vars, no-console */
import {Code, H1, Paragraph} from '@s-ui/documentation-library'
import {Code, H1, ListItem, Paragraph, Strong, UnorderedList} from '@s-ui/documentation-library'

import ColorInheritanceDemo from './ColorInheritanceDemo.js'
import ColorsAndSizesDemo from './ColorsAndSizesDemo.js'
import LazyDemo from './LazyDemo.js'
import PolymorphicDemo from './PolymorphicDemo.js'
import SpanDemo from './SpanDemo.js'
import ArticleA11y from './articles/ArticleA11y.js'
import ArticleColorInheritance from './articles/ArticleColorInheritance.js'
import ArticleColorsAndSizes from './articles/ArticleColorsAndSizes.js'
import ArticleLazy from './articles/ArticleLazy.js'
import ArticlePolymorphic from './articles/ArticlePolymorphic.js'
import ArticleSpan from './articles/ArticleSpan.js'
import {CLASS_SECTION} from './settings.js'

import './index.scss'

export default function () {
return (
<div className="sui-StudioPreview">
<H1>Icon</H1>
<Paragraph>
Icons are small images or symbols used to represent an idea, function, or feature in a graphical user interface
(GUI). They provide a quick, intuitive way to convey information without relying on text.
</Paragraph>
<Paragraph>They can represent:</Paragraph>
<UnorderedList>
<ListItem>
<Strong>Actions:</Strong> such as print, save, or delete
</ListItem>
<ListItem>
<Strong>Objects:</Strong> such as files, folders, or applications
</ListItem>
<ListItem>
<Strong>States:</Strong> such as on, off, or warning
</ListItem>
<ListItem>
<Strong>Navigation:</Strong> such as back, forward, or home
</ListItem>
</UnorderedList>
<Paragraph>
<Code>&#60;AtomIcon&#62;</Code> wraps a <Code>&#60;svg&#62;</Code> that follows the rules defined on the UX
Definition and give you some colors, sizes and interesting features like lazy-load rendering.
</Paragraph>
<ColorsAndSizesDemo />
<ArticleColorsAndSizes className={CLASS_SECTION} />
<br />
<ArticleColorInheritance className={CLASS_SECTION} />
<br />
<ColorInheritanceDemo />
<ArticlePolymorphic className={CLASS_SECTION} />
<br />
<PolymorphicDemo />
<ArticleLazy className={CLASS_SECTION} />
<br />
<LazyDemo />
<ArticleSpan className={CLASS_SECTION} />
<br />
<SpanDemo />
<ArticleA11y className={CLASS_SECTION} />
</div>
)
}
Loading
Loading