Prologue
A while ago, I decided to develop a fully accessible main navigation component in React and write a series of articles documenting the steps it took to create a non-trivial accessible component.
This article is part of the series and marks the first time code will be introduced and discussed. It's all about the base.
I define base components as components that wrap elements that ultimately render to plain HTML from React. If your base components don't take accessibility into consideration, achieving accessibility becomes infinitely harder. A website or application can only be as accessible as its base components allow.
Note: This article is one of a series demonstrating how to build a React navigational component from scratch while considering accessibility through the process. The articles are accompanied by a GitHub repository with releases tied to one or more articles; each building on the previous, until a fully implemented navigation component is complete.
Each release and its associated tag contain fully runnable code for the article. The code discussed in this article is available in the release. and may be downloaded at release 0.2.0. A page showcasing these base components may be run locally through this release.
While code examples are written in JavaScript for brevity, all actual code is written in Typescript and targets React 19.x. Examples use Next.js 16.x, which is not required to run the navigation component.
Follow along either by downloading the release and running the examples while examining the codebase, or by activating the link accompanying each code snippet to view the full file on GitHub.
Content Links
- Introduction
- Base Components
- Text Component
- Icon Component
- List and ListItem Components
- Button Component
- Link Component
- Box Component
- Summary
Introduction
Universal accessibility depends on structural and semantic HTML. Semantics convey meaning to screen readers and other assistive technologies. No matter what language or framework is ultimately used, in the end, every user interface component ultimately renders into plain HTML Elements, which, for all intents and purposes, exposes the same information across browsers and devices. Whether a component or a page, your code can only be as accessible as the base components it is built on.
Why do developers love divs so much? Why is the use of divs and classes so prevalent when structural HTML conveys more information, and the underlying components are built within browsers to be accessible?
Divs are flexible, with no formatting applied. They have no specified height or width, and no padding or margins; they hug their content tightly. They make styling easier, since they aren't subject to the style choices of third-party component libraries or the underlying browser style sheets.
But what if almost every structural HTML component was available with styling stripped out? What if almost every HTML element could be set to be as flexible as a div, with no height or width, no specified borders and no padding. What if each HTML element hugged its content as tightly as a div does? Would you use it then?
An accessible website limits the use of <div />'s to containers for positioning or the creation of custom widgets. <div />, like its cousin <span /> conveys no semantic meaning. If a <div /> is used as the basis for a custom widget, it needs to pass both role and label.
In a previous article, Modalities of Accessibility, I discussed applying the first two principles of the Web Content Accessibility Guidelines through the lens of different peripherals. How something operates has little if anything to do with how it is perceived. When developing a component, I have no opinion on its styling; my focus is on ensuring it meets the stated operable requirements. Styling and design are separate concerns from coding for operability.
Base Components
Fixing and Expanding Accessibility and Functionality
Base components can be imported from a third-party library or written in-house. They wrap around and ultimately render a single HTML element. In either case, modifications can be made to fix issues, expand and enhance functionality, and make it easier for developers to achieve accessibility when using these components.
Using structural HTML and Accessible Rich Internet Applications (ARIA) is part of the toolkit when creating an accessible website, but knowing when to use those tools is just part of it. Developers need to know when it's appropriate to use an aria attribute and when it's not. There's a reason the phrase "No Aria is better than Bad Aria" is widely known.
In some cases, existing operability can actually interfere with accessibility. Consider the <button disabled="true" /> situation. Every browser/screen reader combination exhibits the same issue. If a button is disabled by setting the disabled attribute, it is invisible to a screen reader when the user navigates the screen when using Tab. Any button built will have this issue unless it is addressed in the base component. This issue will be examined when discussing the Button component.
The base components I'll be using in this demo will differ from the components you will have available to you. I'm using a combination of react-aria-components, a Next.js link, and even rolling a few of my own. Consider this article and release as roadmaps to help you enhance your own components.
<nav>
<ul>
<li><a href="#" id="item-one">Item One</a></li>
<li><a href="#" id="item-two">Item Two</a></li>
<li>
<button id="item-three">Item Three</button>
<ul id="subnav-1">
<li><a href="#" id="item-four">Item Four</a></li>
<li><a href="#" id="item-five">Item Five</a></li>
<li>
<button id="item-six">Item Six</button>
<ul>
<li><a href="#" id="item-seven">Item Seven</a></li>
<li><a href="#" id="item-eight">Item Eight</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
[HTML output structure for a navigation component]
When examining the agreed-upon HTML navigation structure, the following Base components are required.
- <List /> - (renders <ul /> or <ol />)
- <ListItem /> - (renders <li />) valid only when a direct child of <List />
- <Button /> - (renders <button />
- <Link /> - (renders <a href />
The Link component also contains other components:
- <Icon /> - (renders a react-icon) - used in the <Link /> component.
- <Text /> - (renders <p /> or <span />) wraps around text not directly contained within a component.
Additionally, I'll address the creation of a < Box /> component, as it requires special handling when creating composite widgets.
Some of these components (Button, Link, and Text) will wrap components from third-party libraries, and other components (List, ListItem, Box) will wrap HTML elements. Additional functionality, either security- or accessibility-focused, will be applied to most of these components.
Where do the accessibility requirements for these components come from? Some of the requirements are lifted from the Web Accessibility Checklist from Deque. Additional requirements are in place to enhance the developer experience and make it easier for them to apply accessibility features specific to the component.
The requirements can be found in the Accessible Base Component Requirements spreadsheet and are also listed in this article under the appropriate component.
A Word on Typing and Testing
The code in the repository is fully typed and tested against the acceptance criteria. Typing is removed from the example code presented in the articles for brevity, but the code required through typing is included. A test file is created alongside the component, and each test is initially set up to conform to one or more acceptance criteria. While I won't detail all the tests in the article, the first component discussed will present a test skeleton, and the full tests are a part of each release.
Text Component
GitHub (release 0.2.0) - components/base/Text
A paragraph has semantic meaning. It's a block of text containing related sentences with distinct beginnings and ends. On a screen, we can discern different paragraphs by the vertical margins between them. A screen reader uses <p /> as an indication to pause before reading the next paragraph. And while a <span /> has no semantic meaning, it's useful for styling or marking up text with attributes, such as language
changes, denoting an abbreviation or using another phrase control element to add semantic meaning into text content.
My text component wraps around its undocumented namesake in react-aria-components. The main thrust of the code is to provide enhancements that allow a screen reader to access information without displaying it on screen. Feel free to use the code I provide to enhance your own text component.
Acceptance Criteria - Text Component
- Text AC 1 - Containing elements are restricted to <p> (default) and <span>.
- Text AC 2 - Text may be hidden from view while still available to a screen reader.
Many component libraries, including react-aria-components, allow their Text component to be rendered as any of several HTML elements, which can be passed in. I'm not fond of the approach since the odds of creating a fully accessible component that works seamlessly as the element sent through aren't very good. The underlying react-aria-component will be used to keep consistency, while restricting it to paragraphs and spans.
In development, situations arise when an aria-label or aria-describedby cannot provide screen readers with an equitable experience, and additional text, not visible on the screen, is useful. A number of component libraries include a <Hidden /> or <VisuallyHidden /> component for these circumstances, but I've found it makes more sense to just expand the functionality of Text.
Testing Skeleton
As mentioned earlier, testing is integral to component development. The acceptance criteria are mapped to the test, and tests are fleshed out as the component is built. The initial test file for Text is shown below.
describe("<Text />", () => {
it.skip("should be WCAG compliant as a Phrase control", async () => {
/* Conforms to Text AC 1 */
});
it.skip("should be WCAG compliant as a Flow control", async () => {
/* Conforms to Text AC 1 */
});
it.skip("should load as inline", () => {
/* Conforms to Text AC 1 */
});
it.skip("should be visually hidden when isHidden is true", () => {
/* Conforms to Text AC 2 */
});
});
Most testing begins with a simple test to confirm a component is mounted, but I've found that substituting automated accessibility testing using jest/axe for the mount makes more sense. While automated accessibility testing can only catch objective issues, such as when interactive items are nested within one another or when something lacks a label, using it as a first test can still help catch issues early. Automated axe testing cannot evaluate subjective criteria, such as whether labels or text adequately convey the required information, but it's a step in the right direction.
Code
export default function Text({
children,
cx,
isHidden,
IsInline,
testId,
...rest}
) {
const textProps = {
...rest,
className: classNames({ srOnly: isHidden }, cx),
"data-testid": testId,
elementType: isInline ? "span" : "p",
};
return <RACText {...textProps}>{children}</RACText>;
}
GitHub (release 0.2.0) - Text.tsx
Two props are added to enhance the underlying component:
When isHidden is set to true, a class of .srOnly is sent into the component, effectively removing the component from the screen, while still keeping it in the DOM.
The isInline property restricts rendering to < span /> or <p />. A span is considered phrase control, which means it can be used within text content and is typically used with a
display: inlineor one of its offshoots. Paragraphs are considered flow control and always start on a new line.
Any attribute passed to my components that isn't explicitly pulled out is sent to the HTML element. This allows any specific aria attributes to be passed through without having to specify each one. Linting will throw an error if a non-viable HTML attribute is passed in.
Icon Component
GitHub (release 0.2.0) - components/base/Icon folder
There are a variety of ways to add icons into your application. I use the react-icons library; your code might use another library, such as FontAwesome or hand-built SVGs. While the react-icons library can be used directly, I prefer to wrap the icons into their own component to extend its functionality. The <Icon /> component receives the icon it is to display and applies accessibility checks.
Acceptance Criteria
- Icon AC 1 - When not decorative or duplicative of meaning, an icon shall have a label describing its function.
- Icon AC 2 - When an icon is decorative or the meaning is already expressed by the element wrapping the icon, the icon shall be hidden from the screen reader.
- Icon AC 3: Either a label or a directive to hide the icon from screen readers is required, not both.
Icons are images and, as such, need to be labeled when the information they convey isn't already available. If the meaning is already available through a parent element, such as an aria-label on a button that wraps the icon, then the icon should be hidden from screen readers since the information it conveys is already available. Any description of an icon should not describe what it looks like; rather, it should describe the
functionality it conveys.
AC 1 and AC 2 describe two competing requirements. Either a label is necessary and should be passed in, or the label is not necessary, and the icon should be silent and hidden from screen readers.
Code
export default function Icon({
cx,
IconComponent,
isSilent,
label,
testId,
}) {
let proceed = true;
if (
process.env.NODE_ENV === "test" ||
process.env.NODE_ENV === "development"
) {
if (!isSilent && !label) {
console.error(
"Dev Error: (Icon) - WCAG 1.1.1: Label must be provided when isSilent is not set.",
);
proceed = false;
}
if (isSilent && label) {
console.error(
"Dev Error: (Icon) - WCAG 1.1.1 Label may not be defined when isSilent is set to true.",
);
proceed = false;
}
}
if (proceed) {
const iconProps = {
"aria-hidden": returnTrueElementOrUndefined(!!isSilent),
"aria-label": label,
className: classNames("svg-icon", cx),
"data-testid": testId,
role: "graphics-symbol",
};
return (
<>
<IconComponent {...iconProps} />
</>
);
}
}
GitHub (release 0.2.0) - Icon.tsx
The acceptance criteria state that either a label is present or isSilent is true, which means neither prop can be required. Since both props are conditional, console errors are thrown when neither condition is met or when both conditions exist, and the component returns nothing in development or testing environments. To render an icon, a developer must meet the acceptance criteria conditions. Checks are ignored in production, so the icon will always be rendered.
It's fairly easy to set any icon without a label to aria-hidden; however, not every developer understands the need to ensure a label is present when an icon is not decorative and is not associated with text that will substitute for labeling. Simply hiding all labels when none are passed is a sure way to fail an accessibility audit. Instead, a developer is reminded to either explicitly set isSilent or to pass a label.
Adding a check like this and preventing rendering when conditions aren't met is necessary. I've seen developers ignore or actively try to suppress console error messages without actually fixing the issue when the component renders with the error. Exposing error messages in a development environment serves as a reminder to developers that accessibility needs to be considered.
An icon's role is "graphics-symbol," and so is explicitly set across any icon.
"aria-hidden" in IconProps calls returnTrueElementOrUndefined(), a custom function, to return undefined if isSilent is false. It's used because several aria attributes should not be sent when the boolean value is false.
To illustrate this point, consider a grouping of ten checkboxes, of which two are checked, exposing an aria-checked="true". When a Boolean value is passed, and the other eight are set to "false," then a screen reader will announce the check status for every item, resulting in aural clutter. ("Checkbox 1, checked. Checkbox 2, unchecked. Checkbox 3, unchecked.") By sending aria-checked only when true, only the selected checkboxes will be announced as checked, while the others will not. ("Checkbox 1, checked. Checkbox 2, Checkbox 3).
I wrote the "returnTrueElementOrUndefined" function to replace the alternative "aria-hidden": isSilent ? true : undefined scenario, which requires testing for both situations everywhere it's used. By utilizing this function, I'm relieved of the requirement to test every component for undefined scenarios.
You might be wondering why an aria-label is being used instead of using a <title />. One reason is that the title isn't easily exposed in the icon library I'm using. Depending on how it's used, SVG titles are primarily used for tooltips on mobile devices, and that implementation is inconsistent. A more consistent approach is to use the aria-label attribute.
List and ListItem Components
GitHub (release 0.2.0) - components/base/List folder
The entire navigation component will consist of unordered lists, links and buttons. This example wraps the HTML elements required for lists.
Acceptance Criteria
- List AC 1 - Lists must be constructed using the appropriate semantic markup with either ul (default) or ol surrounding list items.
- List AC 2 - The list may be displayed horizontally or vertically.
The HTML standard states that the only direct child element of either an unordered or ordered list is a list item (<li />). Checking for this will be handled by jest/axe testing.
The second acceptance criterion just makes it easier to render a list in either a vertical (default) or horizontal orientation.
Code
export default function List({
children,
cx,
isOrdered = false,
orientation = "vertical",
testId,
...rest
}) {
const listProps = {
...rest,
"data-orientation": orientation,
"data-testid": testId,
className: cx,
};
return !isOrdered ? (
<ul {...listProps}>{children}</ul>
) : (
<ol {...listProps}>{children}</ol>
);
}
GitHub (release 0.2.0) - List.tsx
Since lists may be ordered or unordered, the system returns the correct HTML element based on a boolean flag. Unordered lists are the default because they are used more frequently than their ordered counterparts. Lists can also be displayed horizontally or vertically, depending on a dataset prop.
For those who have an awareness of aria, you might be wondering why I'm including a data-orientation and not an aria-orientation. It's because the aria-orientation property is only used in specific roles, and neither list nor listitem supports the role.
export function ListItem({
children,
cx,
testId,
...rest
}){
const listItemProps = {
...rest,
className: cx,
"data-testid": testId,
};
return (
<>
<li {...listItemProps}>{children}</li>
</>
);
}
GitHub (release 0.2.0) - ListItem.tsx
The ListItem component simply wraps the <li /> element. Providing a simple component remains beneficial, as properties, such as cx instead of className, can be standardized.
Button Component
GitHub (release 0.2.0) -components/base/Button folder
Acceptance Criteria
- AC 1 - Every button shall have an accessible label
- AC 2 - Click, Space or Enter activates the button
- AC 3 - When disabled, aria-disabled should be set to true and the event handler should be dissociated from the button.
Every button must have a label, either as text in its children or via an aria-label, especially when the button surrounds an image. Buttons should be activatable with a pointer or the keyboard.
The last acceptance criterion requires more explanation.
HTML allows the disabled attribute to be applied to many interactive elements. Most browsers handle it in a way that causes issues in screen readers. When interactive elements are marked as disabled through the disabled attribute, screen readers will only acknowledge their presence in the elements list/rotor forms control panel. A disabled element is not present at all when tabbing through a page's focusable elements.
This disparity creates a disconnect in perceivability between the screen and the screen reader. It forces a screen reader user to hunt for a disabled, focusable element in the elements list/rotor rather than navigating the DOM directly. This can be considered an equitable use failure, since a screen reader user would have to realize the button is missing and then jump back and forth between the element list/rotor and the page.
When someone is navigating visually, they can still see that a button exists, even if it is disabled. The visual styling, inability to click, and cursor changes help solidify their understanding.
A screen reader user who chooses to navigate a page or form using the tab key may have no idea the disabled control even exists. Instead, the button and its disabled state are only available through the elements list/rotor, and the user must know to check it.
Why do we disable focusable components rather than removing them? The message sent to someone viewing a screen is that something may require their attention to enable it. Perhaps a submit button that only enables when every field in the form meets the requirements (which I do not recommend). How can a user dependent upon screen reader software determine how to enable a button if they are unaware of its existence?
The solution to this issue is to reframe the disabled state by omitting the disabled attribute and event handler, and using the aria-disabled attribute instead. CSS can be styled based on the aria-disabled attribute, and the button will be available to both screen readers and the screen when a user navigates the DOM.
Code
export default function Button({
children,
cx,
isDisabled,
onPress,
testId,
...rest
}) {
const buttonProps = {
...rest,
"aria-disabled": returnTrueElementOrUndefined(!!isDisabled),
className: cx,
"data-testid": testId,
onPress: returnTrueElementOrUndefined(!isDisabled, onPress),
};
return <RACButton {...buttonProps}>{children}</RACButton>;
}
GitHub (release 0.2.0) - Button.tsx
When isDisabled is set to true, the "aria-disabled" attribute is set on the props, and onPress is removed.
The onPress property combines keyboard, click and pointer handlers and is specific to the component library I'm using and also demonstrates another use of returnTrueElementOrUndefined: it returns the onPress event only when the button isn't disabled. The isDisabled prop is a valid prop to send to the react-aria-component button, but since sending it triggers the disabled attribute on the button, it is never sent through.
If you are using another component library for your button, you might need to send a single handler to each of the onClick, onKeyDown and onPointerDown events to achieve the same effect.
Styling can be applied through the aria-disabled attribute in the stylesheet. Any focusable element should select state changes based on either the value of an aria or pseudo-classes. Additional data attributes may also be used if an appropriate pseudo-class or aria attribute isn't available.
Link Component
GitHub (release 0.2.0) - components/base/Link folder
Acceptance Criteria
- AC 1 - Links must be constructed using an <a /> element with a valid href.
- AC 2 - Links may only take a user to another location and may not be used for button-type functionality
- AC 3 - A link must have programmatically discernible text.
- AC 4 - The link should indicate if it will launch a new tab or window.
- AC 5 - All hrefs should be sanitized before being exposed
The Link component wraps the link provided by Next/js, which itself extends the <a /> HTML component with a few router-based props.
SEO emphasizes the use of links, not buttons, for navigation. Links and buttons have different semantic meanings and cannot be used interchangeably. A link's sole purpose is to navigate a user to another page, either to a section on the same page they are already on (an anchor) or to an entirely new page, either within or outside the current site. A button executes a process on the current page and, when activated, may or may not redirect the user to another page upon completion.
When a link opens a new browser tab or window to load a new page, it should notify the user before the link is activated. Failure to do so, especially when a user relies on a screen reader, can leave them disoriented and unable to determine what has changed or even where they are relative to their last action. Typically, an icon with a label, or simply hidden text, should indicate that the link opens in a new browser tab or window before the user activates it.
For security, every href should be sanitized to strip out any malicious code inserted.
Code
export function Link({
children,
cx,
href,
newTabText = "opens in a new tab",
openInNewTab,
ref,
suppressNewIcon,
target,
testId,
...rest
}) {
const { getLinkTarget, getIsTargetSpecific, getNewTab, getSafeHref } =
useLink();
const safeHref = getSafeHref(href);
// ...
}
GitHub (release 0.2.0) - Link.tsx
Most cybersecurity attacks start with a simple link, so securing links within the component that renders them is important. If you're not sure whether your link component sanitizes the href it receives, find out; if it doesn't, add a check to your component.
In this case, several utility functions are stored in the useLink hook, including getSafeHref.
export default function useLink() {
const getSafeHref = (href) => {
if (!!href && href.length > 0) {
return sanitizeUrl(href);
}
return;
};
}
GitHub (release 0.2.0) - useLink.tsx - getSafeHref() - Line 44
If the href exists, then a third-party utility from braintree is used to remove any compromising content. It runs whenever an href is passed through.
export function Link() {
//...
const linkTarget = getLinkTarget(openInNewTab, target);
const isTargetSpecific = getIsTargetSpecific(linkTarget);
//...
}
GitHub (release 0.2.0) - Link.tsx
Returning to the Link component, more calls are made to the hook, specifically regarding the criteria for opening in new browser tabs or windows and the required output.
A named or standard target can be passed to the link component. When a new browser tab or window is opened, any subsequent links to a named target will open in the same tab or window. Only when the target is not already available will a new tab open. Targets may include custom names or standardized keywords, which are always prefaced with an underscore.
const getLinkTarget = ( openInNewTab, target ) => {
if (target) {
return target;
} else {
return openInNewTab ? "_blank" : "_self";
}
};
GitHub (release 0.2.0) - useLink.tsx - getLinkTarget() - Line 15
A target is a separate entity from _blank, which requests a new browser tab, or _self, which loads the content into the active tab.
const getIsTargetSpecific = (linkTarget) => {
const nonTargeted = ["_parent", "_self", "_top"];
return nonTargeted.indexOf(linkTarget) === -1;
};
GitHub (release 0.2.0) - useLink.tsx - getIsTargetSpecific() - Line 8
Once a linkTarget is known, the system returns information to determine whether to open a new browser tab. Some standardized target types are not candidates, and they need to be accounted for.
export function Link() {
//...
const willOpenInNewTab = openInNewTab || isTargetSpecific;
const newTab = getNewTab(newTabText, !!suppressNewIcon || !!target);
const linkProps = {
...rest,
className: cx,
"data-testid": testId,
href: safeHref || "",
target: linkTarget,
};
return (
<NextLink {...linkProps}>
{children}
{willOpenInNewTab && newTab}
</NextLink>
);
}
GitHub (release 0.2.0) - Link.tsx
A tab will open in a new window only if the functionality is explicitly requested via openInNewTab or when the target is deemed specific.
const getNewTab = ( suppressNewIcon, newTabText ) => {
const iconProps = {
IconComponent: NewWindowIcon,
label: newTabText,
};
if (suppressNewIcon) {
return (
<Text isInline={true} isHidden={true}>
{newTabText}
</Text>
);
} else {
return <Icon {...iconProps} />;
}
};
GitHub (release 0.2.0) - useLink.tsx - getNewTab() - Line 25
While it's preferable to display information on-screen when a link opens a new tab, an icon may not always fit the system design. In that case, hidden text is placed in the DOM instead.
Box Component
GitHub (release 0.2.0) - components/base/Box folder
While I don't call < Box /> in the navigation component, I thought it was worth going through because it's such a common use case and, like the Icon component, can help guide developers toward proper ARIA usage.
Acceptance Criteria
- AC 1 - If a role is not passed through on a div, then no aria may be present.
- AC 2 - If a role is passed through on a div, then a label or reference to a label must be present unless the role is "presentation" or "none".
A Box can render as a < div />, denoting a flow control or a < span />, which is a phrase control. For the most part, both < div /> and < span /> are non-semantic elements, meaning they don't add anything to the conversation. They're useful for grouping and adding spacing between elements and denoting a custom widget with an explicit role.
Every structural HTML element other than < div /> or < span /> contains an inherent role. Composite widgets start with a < div /> to which a role has been explicitly applied. Think of dialog boxes, progress bars, and switches. A Box component must support the implementation of a composite widget, including ARIA, while ensuring ARIA is not applied when a role is absent.
Code
export default function Box(props) {
const { children, cx, inline, isHidden, label, role, testId, ...rest } = props;
let proceed = true;
if (
(!inline &&
process.env.NODE_ENV === "test") || process.env.NODE_ENV === "development"
) {
const ariaLabelledby = props["aria-labelledby"];
const ariaLabel = props["aria-label"];
const ariaRole = props["aria-role"];
const excludedRoles: AriaRole[] = ["presentation", "none"];
// If the role or aria role doesn't exist or has no meaning, then no aria can be passed.
if (!role && !ariaRole) {
const ariaFound = Object.fromEntries(
Object.entries(props).filter(([key]) => key.startsWith("aria-")),
);
if (Object.keys(ariaFound).length > 0) {
console.error(
"Dev Error: (Box) - Aria attributes may not be passed when no role is defined.",
);
proceed = false;
}
} else if (!label && !ariaLabel && !ariaLabelledby) {
if(!excludedRoles.includes(role) && !excludedRoles.includes(ariaRole) )
console.error(
"Dev Error: (Box) - Must pass label, aria-label or aria-labelledby when a role is set.",
);
proceed = false;
}
// check for singular label
if (role && ((label && ariaLabel) || (label && ariaLabelledby) || (ariaLabel && ariaLabelledby))) {
console.error(
"Dev Error: (Box) - Only one of label, aria-label or aria-labelledby may be passed when a role is set.",
);
proceed = false;
}
}
// ...
}
GitHub (release 0.2.0) - Box.tsx
Like the Icon component, the Box component only renders in development and test environments when conditions that can't be enforced solely through Typescript are met. The idea remains the same: educate developers on what makes good accessibility.
Because aria attributes are not easily accessed through destructuring, and the component has no idea of what is being passed, the props component is passed through and destructured separately, while the system checks to see if any aria- prepended variables have been passed through.
const componentProps = {
...rest,
className: classNames({ srOnly: isHidden }, cx),
"data-testid": testId,
}
const divProps ={
"aria-label": label || props["aria-label"],
"aria-labelledby": props["aria-labelledby"],
role,
};
if (proceed) {
return inline ? (
<>
<span {...componentProps}>{children}</span>
</>
) : (
<>
<div {...componentProps} {...divProps}>{children}</div>
</>
);
}
GitHub (release 0.2.0) - Box.tsx
Labels can be passed in a few ways; one is through an exposed "label" prop, which generates an aria-label. An aria-label can still be passed through and used when the label is undefined. If another element is tagged as the label, the id of that element is passed via aria-labelledby. Only one of the three may be passed and used, so the earlier checks enforce that supposition.
Summary
Base components can enhance and enforce security and accessibility. Code can be added to enable developers to provide accessibility easily and to prevent common antithetical choices.
Top comments (0)