arrow-left

Only this pageAll pages
gitbookPowered by GitBook
triangle-exclamation
Couldn't generate the PDF for 239 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

DUKE

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Site Navigation

Navigate my docs

hashtag
🗺 Site Navigation

arrow-up-right

hashtag
📖 Storybook

Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

To get it up and running navigate to the app repo and, from the command line, enter:

Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

hashtag
Stories

A story, much like a React component, is a function that describes how to render a component.

You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

So here's how the initial default PushDownPanel might look:

And then we can add variations based on different states, for example a version that uses icons:

And the new stories will show up in the sidebar navigation, like so:

hashtag
Args

Story definitions can be further improved to take advantage of Storybook's "args" concept.

A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

hashtag
Controls

Controls allow designers and developers to explore component behavior by mucking about with its arguments.

Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

A select menu to control the number of items that render:

And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

hashtag
A11y and Other Addons

Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

hashtag
Anatomy of a Story

Stories exist alongside the other component files as stories.js.

Here's an example of how a typical story might take shape:

Let's break this down line by line:

Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

hashtag
storybookCustomArgs

The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

hashtag
createStoryOptions

Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

hashtag
Resources

  • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

  • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

  • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

Self Link

hashtag
Duke Training:

query
searches
hits
pageHits
sectionHits

Website Navigation


hashtag
Table of contents

hashtag
General Info

hashtag
Sitecore

DNT-2843 EVCalculator

hashtag
Controls:

Name
Description
Default
Control

inputs*

Udemy

https://www.udemy.com/course/react-and-typescript-build-a-portfolio-project

InputsType

-

-

output*

YearlyOutputProps

-

-

analytics

ComponentEvent

-

-

backgroundColor

string

-

-

bgColorClass

"""bg-gray-lighter"

-

-

ctaText

any

-

-

fullWidth

boolean

-

-

image

any

-

-

isCtaAButton

boolean

-

-

link

any

-

-

mobileLink

any

-

-

modal

{ id: string; url?: string; } | null

-

-

subtitle

any

-

-

title

any

-

-

file-archive
4KB
EVCalculator.zip
archive
arrow-up-right-from-squareOpen
file-archive
8KB
Calculator.zip
archive
arrow-up-right-from-squareOpen
//Calculator/index.tsx

import React from "react";
import Input from "./inputComponents/Input";
import Stepper from "./inputComponents/Stepper";
import Slider from "./inputComponents/Slider";

const Calculator = () => (
  <section className="px-16 md:px-24 py-32 md:py-48">
    <div className="mb-48 items-center flex">
      <Slider
        prompt="Prompt goes here"
        defaultVal="88888"
        min="0"
        max="100000"
        label="Number Slider Title"
      />
    </div>
    <div className="flex flex-1">
      <Stepper prompt="Prompt goes here" defaultVal="1" />
      <Input
        prompt="Prompt goes here"
        defaultVal="500"
        label="per month"
        isInDollars={true}
      />
    </div>
  </section>
);

export default Calculator;
//src/components/Calculator/outputComponents/YearlySavings.tsx

import { YearlyOutputProps } from "../types";

const avgDaysInMonth = 30.437;
const avgDaysInYear = 365.25;

const formatMoney = (money: number) => {
  const roundedNum = Math.round(money * 100) / 100;
  const arr = roundedNum.toLocaleString().split(".");
  const cents = `${arr[1] || ""}00`.slice(0, 2);
  const dollars = arr[0];
  return {
    dollars,
    cents,
    full: `$${dollars}.${cents}`,
  };
};

const YearlySavings = ({
  dailySavings = 0,
  headline = "Total Yearly Savings",
  monthlyText = "Monthly Savings",
  dailyText = "Daily Savings",
  description,
}: YearlyOutputProps) => {
  const annual = formatMoney(dailySavings * avgDaysInYear);
  const monthly = formatMoney(dailySavings * avgDaysInMonth);
  const daily = formatMoney(dailySavings);

  return (
    <div>
      <h4 className="text-blue text-xl-fixed">{headline}</h4>
      {/* need to adjust line height to vertically top align */}
      <p
        className="text-blue my-12 text-2xl-fixed align-top"
        style={{ lineHeight: "55px" }}
      >
        $
        <span className="text-3xl-fixed align-top leading-none">
          {annual.dollars}
        </span>
        {`.${annual.cents}`}
      </p>
      <div className="flex flex-row justify-center">
        <div className="border-r px-32 border-gray">
          <p
            className="text-blue align-top text-lg-fixed"
            style={{ lineHeight: "25px" }}
          >
            $<span className="text-xl-fixed align-top">{daily.dollars}</span>
            {`.${daily.cents}`}
          </p>
          <p className="text-gray-dark text-xs">{dailyText}</p>
        </div>
        <div className="px-32">
          <p
            className="text-blue align-top text-lg-fixed"
            style={{ lineHeight: "25px" }}
          >
            $<span className="text-xl-fixed align-top">{monthly.dollars}</span>
            {`.${monthly.cents}`}
          </p>
          <p className="text-gray-dark text-xs">{monthlyText}</p>
        </div>
      </div>
      {description && (
        <p className="mt-32 text-gray-dark text-md">{description}</p>
      )}
    </div>
  );
};

export default YearlySavings;
//src/components/Calculator/test.tsx

import "@testing-library/jest-dom";
import Input from "src/components/Calculator/inputComponents/Input";
import Slider from "src/components/Calculator/inputComponents/Slider";
import YearlySavings from "./outputComponents/YearlySavings";
import { renderWithCTX, screen, fireEvent } from "src/lib/testWrappers";

const props: Parameters<typeof Input>[0] = {
  prompt: "Current Gas Price",
  defaultVal: "3.12",
  label: "per gallon",
  isInDollars: true,
};

describe("calculator input", () => {
  it("should render", () => {
    renderWithCTX(<Input {...props} />);
    const input = screen.getByRole("textbox", {
      name: `${props?.prompt} ${props?.label}`,
    });
    expect(input).toBeInTheDocument();
    expect(input).toHaveAttribute("value", props.defaultVal);
  });
  it("should prefix with $ if applicable", () => {
    const { rerender } = renderWithCTX(<Input {...props} />);
    let currency: HTMLElement | null = screen.getByText(/\$/i);
    expect(currency).toBeInTheDocument();
    rerender(<Input {...props} isInDollars={false} />);
    currency = screen.queryByText(/\$/i);
    expect(currency).not.toBeInTheDocument();
  });
});

describe("calculator slider input", () => {
  const props = {
    prompt: "How many miles do you drive daily?",
    defaultVal: "160",
    label: "Daily Miles",
    min: "0",
    max: "320",
    step: "1",
  };
  it("should render", () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    const input = screen.getByRole("textbox", {
      name: `${props.prompt} ${props.label}`,
    });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole("heading", {
      name: /how many miles do you drive daily\?/i,
    });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it("should default to midpoint between min and max when defaultVal is missing", () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    expect(slider).toHaveAttribute("value", "200");
  });
  it("should keep the same slider and text input values", () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    const input = screen.getByRole("textbox", {
      name: `${props.prompt} ${props.label}`,
    });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute("value", "28");
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute("value", "140");
  });
});

describe("yearly savings output", () => {
  const props = {
    headline: "Yearly Savings",
    monthlyText: "Monthly Savings",
    dailyText: "Daily Savings",
    dailySavings: 2.22,
  };
  it("should render", () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole("heading", { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it("should properly display rounded monetary amounts", () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText("8");
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});
// Stepper Test.tsx



import '@testing-library/jest-dom';
import Input from 'src/components/Calculator/inputComponents/Input';
import Slider from 'src/components/Calculator/inputComponents/Slider';
import YearlySavings from './outputComponents/YearlySavings';
import { renderWithCTX, screen, fireEvent } from 'src/lib/testWrappers';

const props: Parameters<typeof Input>[0] = {
  prompt: 'Current Gas Price',
  defaultVal: '3.12',
  label: 'per gallon',
  isInDollars: true,
};

describe('calculator input', () => {
  it('should render', () => {
    renderWithCTX(<Input {...props} />);
    const input = screen.getByRole('textbox', {
      name: `${props?.prompt} ${props?.label}`,
    });
    expect(input).toBeInTheDocument();
    expect(input).toHaveAttribute('value', props.defaultVal);
  });
  it('should prefix with $ if applicable', () => {
    const { rerender } = renderWithCTX(<Input {...props} />);
    let currency: HTMLElement | null = screen.getByText(/\$/i);
    expect(currency).toBeInTheDocument();
    rerender(<Input {...props} isInDollars={false} />);
    currency = screen.queryByText(/\$/i);
    expect(currency).not.toBeInTheDocument();
  });
});

describe('calculator slider input', () => {
  const props = {
    prompt: 'How many miles do you drive daily?',
    defaultVal: '160',
    label: 'Daily Miles',
    min: '0',
    max: '320',
    step: '1',
  };
  it('should render', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole('heading', { name: /how many miles do you drive daily\?/i });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it('should default to midpoint between min and max when defaultVal is missing', () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    expect(slider).toHaveAttribute('value', '200');
  });
  it('should keep the same slider and text input values', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute('value', '28');
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute('value', '140');
  });
});

describe('yearly savings output', () => {
  const props = {
    headline: 'Yearly Savings',
    monthlyText: 'Monthly Savings',
    dailyText: 'Daily Savings',
    dailySavings: 2.22,
  };
  it('should render', () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole('heading', { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it('should properly display rounded monetary amounts', () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText('8');
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});



describe('calculator slider input', () => {
  const props = {
    prompt: 'How many miles do you drive daily?',
    defaultVal: '160',
    label: 'Daily Miles',
    min: '0',
    max: '320',
    step: '1',
  };
  it('should render', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole('heading', { name: /how many miles do you drive daily\?/i });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it('should default to midpoint between min and max when defaultVal is missing', () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    expect(slider).toHaveAttribute('value', '200');
  });
  it('should keep the same slider and text input values', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute('value', '28');
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute('value', '140');
  });
});

describe('yearly savings output', () => {
  const props = {
    headline: 'Yearly Savings',
    monthlyText: 'Monthly Savings',
    dailyText: 'Daily Savings',
    dailySavings: 2.22,
  };
  it('should render', () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole('heading', { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it('should properly display rounded monetary amounts', () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText('8');
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});

[Component Story Format](https: //storybook.js.org/blog/component-story-format/) - read more about the Component Story Format (CSF), a new way to author stories based on ES6 modules

4

typesc

1

381

20

361

test

1

262

14

248

nav

1

155

2

153

stepper

1

4

https: //duke-2.gitbook.io/duke
https://duke-3.gitbook.io/duke/arrow-up-right
🎟️Jira Ticketschevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/jira-tickets/dnt-2658%20(1).mdchevron-right
🎫DNT-2659 A meaningful image must have a text alternative that serves the equivalent purposechevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/jira-tickets/dnt-2658-1.mdchevron-right
🧪DNT-2724 Accordion Testingchevron-right
⛔2654-a11y-Audit-Form-Errorschevron-right
🤷Personalchevron-right
📚Docschevron-right
🧱Component Creationchevron-right
Analyticschevron-right
✅DefinitionOfDonechevron-right
🤖TechnicalOverviewchevron-right
🧑‍🏫🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverviewchevron-right
📚Sitecorechevron-right
👨‍⚕️👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Introchevron-right
🚧Setupchevron-right
📚Docschevron-right
ℹ️Formschevron-right
⚛️Reactchevron-right
🌀Tailwind CSSchevron-right
📖Storybookchevron-right
📼Typescriptchevron-right
💡Ideaschevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/broken-reference/README.mdchevron-right
🦿ACCESSIBILITYchevron-right
Ariachevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/ke-energy.mdchevron-right
🗺️Site Navigationchevron-right
🎟️Jira Ticketschevron-right
🛑DNT-2658chevron-right
🎫DNT-2659 A meaningful image must have a text alternative that serves the equivalent purposechevron-right
🧪DNT-2724 Accordion Testingchevron-right
⛔2654-a11y-Audit-Form-Errorschevron-right
🤷Personalchevron-right
📚Docschevron-right
🧱Component Creationchevron-right
Analyticschevron-right
✅DefinitionOfDonechevron-right
🤖TechnicalOverviewchevron-right
🧑‍🏫🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverviewchevron-right
📚Sitecorechevron-right
👨‍⚕️👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Introchevron-right
🚧Setupchevron-right
📚Docschevron-right
ℹ️Formschevron-right
⚛️Reactchevron-right
🌀Tailwind CSSchevron-right
📖Storybookchevron-right
📼Typescriptchevron-right
💡Ideaschevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/broken-reference/README.mdchevron-right
🦿ACCESSIBILITYchevron-right
Ariachevron-right
🖨️Typescriptchevron-right
Typescript Typeschevron-right
Types VS Interfaceschevron-right
Typescript Interfaceschevron-right
Enumschevron-right
Types VS Interfaceschevron-right
Typescript Rules:chevron-right
🦽Accessabilitychevron-right
Focus Orderchevron-right
🦾ARIA - Accessibilitychevron-right
⚙️Testingchevron-right
🎹Testing Inputchevron-right
🧪React Testing Librarychevron-right
⚒️Most Usefulchevron-right
🌪️Tailwind Cheatsheetchevron-right
🔖Bookmarkschevron-right
🖥️Websitechevron-right
😀Overviewchevron-right
Duke Energy Manual Audit_Reportchevron-right
Live Deploychevron-right
⚒️CREATING COMPONENTSchevron-right
ℹ️Introchevron-right
🧑💻 TechnicalOverviewchevron-right
👨‍🔬👨🔬 👨🔬 👨🔬 👨🔬 👨🔬 👨🔬 🔬 Practical Overviewchevron-right
📈Analyticschevron-right
📰Sitecorechevron-right
Sitecore-Docschevron-right
✅DefinitionOfDonechevron-right
⚙️SETUPchevron-right
🧪Testingchevron-right
ℹ️Formschevron-right
⚛️Reactchevron-right
📖Storybookchevron-right
📔Official Tutorialchevron-right
🧱Component Driven Designchevron-right
🗒️Code Splittingchevron-right
🖼️SvgLoaderchevron-right
🌬️Tailwind CSSchevron-right
💯Unit Testschevron-right
📚General Infochevron-right
⚛️REACT NOTESchevron-right
Hooks API Reference - Reactchevron-right
Using the Effect Hook - Reactchevron-right
Getting Started With Reactchevron-right
React ToDoListchevron-right
Componentizing our React appchevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/react-interactivity-events-and-state.mdchevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/react-interactivity-editing-filtering-conditional-rendering.mdchevron-right
Create React Appchevron-right
React Componentschevron-right
Handling Eventschevron-right
React Testing Recipes:chevron-right
🕸️Webpackchevron-right
🥘Curry VS Functional Compositionchevron-right
🛗Argvchevron-right
💬Bubbling & Capturingchevron-right
🔲Moduleschevron-right
Private NPM Packageschevron-right
aria-labelledby - Accessibilitychevron-right
Focuschevron-right
⁉️Optional Chaningchevron-right
💍Promiseschevron-right
🔢Enums In Javascriptchevron-right
🗣️JIRAchevron-right
🥑Airbnb JavaScript Style Guidechevron-right
🤸‍♂️🤸♂ 🤸♂ 🤸♂ 🤸♂ 🤸♂ 🤸♂ Performancechevron-right
🎛️Sitecorechevron-right
😁DXT-Solutionchevron-right
💻Codechevron-right
Index.tsxchevron-right
Composition.tsxchevron-right
Data.jschevron-right
Types.tschevron-right
Stories.jschevron-right
Test.tsxchevron-right
🕐Testing Assignmentchevron-right
🗓️Week 1chevron-right
🗓️Weekschevron-right
📅Day 4chevron-right
Day 2chevron-right
Day 3chevron-right
🗓️Week 2chevron-right
🗓️Week 3chevron-right
Day 1chevron-right
Day 5chevron-right
Day 4chevron-right
Day 2chevron-right
Day 3chevron-right
🗓️Week 4chevron-right
🗓️Week 5chevron-right

0

DNT-2423 Throttle scroll event listeners

See: src/components/SecondaryNav/index.tsx - L:99

We probably don't need event fired for every single scroll.

chevron-rightSecondary Navhashtag
npm run storybook
export const NoIcons = Template.bind();
NoIcons.args = {
  items: itemsOptions["3"],
};
export const Icons = Template.bind({});
Icons.args = {
  items: itemsOptions["5"],
};
export default {
  title: "Components/Accordion",
  component: AccordionComponent,
  argTypes: {
    theme: {
      name: "Theme",
    },
    closeOthers: {
      name: "One Panel Open At A Time",
    },
  },
};

const Template = (args) => <AccordionComponent {...args} />;

export const Primary = Template.bind({});

Primary.args = {
  ...props,
};
argTypes: {
  backgroundColor: {
    control: {
      type: 'radio',
      options: ['white', 'gray'],
    },
    defaultValue: 'white',
  },
},
argTypes: {
  items: {
    name: 'How many items?',
    control: {
      type: 'select',
      options: {
        ...itemsPrimary,
      },
    },
  },
},
argTypes: {
  title: {
    control: {
      type: 'text',
    },
  },
  subtitle: {
    control: {
      type: 'text',
    },
  },
  link: {
    control: 'boolean',
  },
  ctaType: {
    control: {
      type: 'inline-radio',
      options: ['ImageSlide', 'VideoSlide'],
    },
  },
},
import React from "react";
import MyComponent from "./index";
import { Data } from "./data";
import { MyComponent as MyComponentComposition } from "../../lib/composition";

const props = MyComponentComposition({ fields: Data });

export default {
  title: "Components/MyComponent",
  component: MyComponent,
  argTypes: {
    backgroundColor: {
      control: {
        type: "radio",
        options: ["white", "gray"],
      },
      defaultValue: "white",
    },
  },
};

const Template = (args) => <MyComponent {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  ...props,
};
import React from 'react';
import MyComponent from './index';
import { Data } from './data';

...
...

import { MyComponent as MyComponentComposition } from '../../lib/composition';


const props = MyComponentComposition({ fields: Data });

...
...

export default {
  title: 'Components/MyComponent',
  component: MyComponent,
  argTypes: {
    backgroundColor: {
      control: {
        type: 'radio',
        options: ['white', 'gray'],
      },
      defaultValue: 'white',
    },
  },
};

...
...


const Template = args => <MyComponent {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  ...props,
};
...

import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';


const props = BulletedOverviewComposition(Data);

const itemsToShow = createStoryOptions(props.items);

export default {
  title: 'Components/Bulleted Overview',
  component: BulletedOverview,
  argTypes: {
    ...storybookCustomArgs(props, BulletedOverview, [], false),
    items: {
      name: 'How many items?',
      control: {
        type: 'select',
        options: {
          ...itemsToShow,
        },
      },
    },
  },
};

...
/* eslint-disable complexity */
/* eslint-disable max-lines */
import React, { useState, useRef, useEffect } from "react";
import { SecondaryNavType } from "./types";
import { NavItem } from "./components/NavItem";
import { DropDownMenu } from "./components/DropDownMenu";
import { useLocation } from "react-router-dom";

const SecondaryNav = ({ items, suppressNav }: SecondaryNavType) => {
  const currentItems = items[0];
  const [active, setActive] = useState(-1);
  let { pathname } = useLocation();
  pathname = pathname?.split(" ").join("-").toLowerCase

  const isItemActive = (index: number) => active === index;
  // Need to check if the current page is the route of a top level page or any subpages or subpages' subpages so we can display the glorious teal bar
  const selectedIndex: Array<number> = [];
  let topLevelSelected: number;
  if (currentItems) {
    // check if the current page is one of the top level navItems in the blue bar
    topLevelSelected = currentItems.subpages.findIndex(
      (x) => x.route.toLowerCase() === pathname
    );
    // check if the current page is in any of the subpages
    for (let i = 0; i < currentItems.subpages?.length; 
      const select = currentItems.subpages[i]?.subpages?.findIndex(
        (x) => x.route.toLowerCase() === pathname
      );
      if (select !== -1) {
        selectedIndex.push(i);
        break;
      } else {
        for (
          let ii = 0;
          ii < currentItems.subpages[i]?.subpages[ii]?.subpages?.length;
          ii++
        ) {
          const select = currentItems.subpages[i]?.subpages[
            ii
          ]?.subpages?.findIndex((x) => x.route.toLowerCase() === pathname)
          if (select !== -1) {
            selectedIndex.push(i);
            break;
          }
        }
      }
    }
  }

  const isItemSelected = (index: number) => selectedIndex.indexOf(index) !==

  const hasSubPages = currentItems?.subpages.length > 0;

  const getNumberOfCols = (index: number) => {
    if (currentItems?.subpages[index].subpages[0]?.subpages?.length) {
      return currentItems.subpages[index].subpages.length;
    }
    return 1;
  };

  const dropdownDirection = (index: number) => {
    const navItemsLength = currentItems?.subpages.length;
    const numberOfCols = getNumberOfCols(index);
    const placeInNav = index + 1;
    const displayToRight = navItemsLength - placeInNav >= numberOfCols;
    const displayToLeft = placeInNav > numberOfCols;
    const leftSide = placeInNav <= navItemsLength / 2;

    if (displayToRight) {
      return { side: "left-0", position: "relative", cols:
    } else if (displayToLeft) {
      return { side: "right-0", position: "relative", cols:
    } else if (leftSide) {
      return { side: "left-0", position: "", cols: numberOfCols
    } else {
      return { side: "right-0", position: "", cols: numberOfCols
    }
  };

  const rootElement = useRef<HTMLDivElement | null>(null);
  const activeButton = useRef<HTMLButtonElement>(null);
  useEffect(() => {
    const handleClick = (event: Event) => {
      const cTarget = event.target as Element;
      if (
        (cTarget instanceof HTMLElement &&
          !rootElement.current?.contains(cTarget)) ||
        cTarget.tagName === "A" ||
        cTarget.tagName === "SPAN"
      ) {
        setActive(-1);
      }
    };
    window.addEventListener("click", handleClick);

    return () => {
      window.removeEventListener("click", handleClick);
    };
  }, []);

  useEffect(() => {
    const handlePress = (event: KeyboardEvent) => {
      const { key } = event;
      if (key === "Escape") {
        activeButton?.current?.focus();
        setActive(-1);
      }
    };
    window.addEventListener("keyup", handlePress);

    return () => {
      window.removeEventListener("keyup", handlePress);
    };
  }, []);

  // Close menu when scrolled off screen
  useEffect(() => {
    const handleScroll = ({ currentTarget }: Event) => {
      if (!rootElement.current) {
        return;
      }

      const headerDimensions = rootElement.current.getBoundingClientRect();

      const headerDistanceFromTop = headerDimensions.top;
      const headerHeight = headerDimensions.height;

      const scrollingDown = (currentTarget as Window).scrollY > 0;
      const headerOutOfView = headerDistanceFromTop + headerHeight < 0;

      // offScreen
      const offScreen = headerOutOfView && scrollingDown;
      if (offScreen) {
        // Close menu
        setActive(-1);
      }
    };

    // TODO Throttle event listeners
    window.addEventListener("scroll", handleScroll, { passive: true });

    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  if (suppressNav || !hasSubPages) {
    return null;
  }

  return (
    <div
      className="hidden xl:block xl:-mt-px px-16 md:px-24 bg-blue text-white"
      ref={rootElement}
    >
      <nav className="relative container-4xl h-full" aria-label="Secondary">
        <ul className="flex justify-between -mx-16 h-full">
          {currentItems.subpages.map(({ subpages, ...rest }, index) => {
            const isActive = isItemActive(index);
            const isSelected =
              isItemSelected(index) || topLevelSelected === index;
            return (
              <li className={dropdownDirection(index).position} key={index}>
                <NavItem
                  {...rest}
                  currentRef={isActive ? activeButton : null}
                  isActive={isActive}
                  isSelected={isSelected}
                  onClick={() => setActive(isActive ? -1 : index)}
                  type={subpages.length ? "button" : "link"}
                />
                <DropDownMenu
                  {...{
                    subpages,
                    ...{
                      ...rest,
                      isActive,
                      dropdownDirection: dropdownDirection(index),
                    },
                  }}
                />
              </li>
            );
          })}
        </ul>
      </nav>
    </div>
  );
};

export default SecondaryNav;
Gitbook Action Build

Site Navigation

hashtag
Multi Step Forms

Name

Path

MultiStepFormBuilder

/Home/HealthCheck/Form-Multi/Content/MultiStepFormBuilder

No-Account

hashtag
Related Links

Name
Updated
Updated by
Created
Created by
Path
chevron-righthashtag

    // TODO Throttle event listeners
    window.addEventListener('scroll', handleScroll, { passive: true });

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  if (suppressNav || !hasSubPages) {
    return null;
  }

Docs

NAM\SHaberm

6/6/2016 3:23:06 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Billing/Automatic Payment Plan/Content/Related Links

Related Links

11/8/2018 9:48:25 AM

nam\rvp0696

11/6/2018 10:24:55 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DE Carolinas Rates/Content/Main content container/Related Links

Related Links

11/8/2018 9:46:03 AM

nam\rvp0696

11/6/2018 10:24:55 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DE Progress Rates/Content/Main Content Container DEP SC/Related Links

Related Links

9/30/2019 3:50:08 PM

nam\rvp0696

9/27/2019 11:02:18 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DEC NC Rate Case 2019/Content/Main Content Container/Related Links

Related Links

7/21/2020 1:54:05 PM

sitecore\courtney.icenhour

7/21/2020 1:54:00 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Billing/DEI 2019 Rate Case/Content/Main Content Container/Related Links

Related Links

10/30/2019 1:17:29 PM

nam\rvp0696

9/27/2019 11:02:18 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DEP NC Rate Case 2019/Content/Main Content Container/Related Links

Related Links

9/1/2017 2:19:00 PM

NAM\CBIcenh

8/29/2017 3:20:15 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Billing/Kentucky Rate Case/Content/Multi Column/Related Links

Related Links

1/30/2020 7:47:49 PM

sitecore\Van.Parker

1/30/2020 7:47:47 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Billing/Kentucky Rate Review/Content/Main Copy Container/Related Links

Related Links

8/25/2017 9:50:47 AM

NAM\CBIcenh

8/1/2017 11:03:23 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Billing/North Carolina DEC Rate Case/Content/Multi Column/Related Links

Related Links

9/27/2017 3:08:20 PM

nam\rvp0696

9/27/2017 2:15:28 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/North Carolina Rate Case/Content/Multi Column/Related Links

Related Links

3/22/2021 3:31:13 PM

sitecore\janet.kosoglov

9/1/2016 1:54:17 PM

NAM\LBorder

/sitecore/content/JssPublic/Home/Home/Billing/Rates/Electric Fuel Adjustments/Content/Related Links

Related Links

7/18/2016 10:28:11 AM

NAM\JWells4

7/6/2016 1:07:17 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Billing/South Carolina Rate Case/Content/Related Links

Related Links

8/17/2016 2:58:55 PM

nam\tmitch3

8/11/2016 5:04:38 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Home/Billing/Special Assistance/Assistance Agencies/Content/Multi Column/Related Links

Related Links DEC

4/5/2021 8:07:57 PM

sitecore\janet.kosoglov

4/5/2021 7:52:13 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home/Billing/Special Assistance/Medical Alert/Content/MultiColumn/Related Links DEC

Related Links DEP

4/5/2021 7:52:35 PM

sitecore\janet.kosoglov

7/11/2016 12:48:38 PM

NAM\cball

/sitecore/content/JssPublic/Home/Home/Billing/Special Assistance/Medical Alert/Content/MultiColumn/Related Links DEP

Related Links

10/15/2021 3:05:47 PM

sitecore\jerry.wells

10/15/2021 3:05:44 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/Content/MultiColumn/Related Links

Related Links

10/15/2021 3:06:19 PM

sitecore\jerry.wells

10/15/2021 3:06:16 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/How It Works/Content/Multi Column/Related Links

Related Links

6/1/2021 7:45:29 PM

sitecore\janet.kosoglov

6/17/2016 12:08:29 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/Energy Saving Tips/Content/Multi Column/Related Links

Related Links

7/19/2016 1:51:33 PM

NAM\SHaberm

6/17/2016 12:08:29 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/Rate Comparison/Content/Related Links

Related Links right

3/16/2022 6:57:22 PM

sitecore\courtney.icenhour

3/16/2022 6:55:35 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Start Stop Move/Account Change/Content/Multi Column main/Related Links right

Additional Information

11/16/2019 2:16:13 PM

sitecore\admin

9/13/2016 2:27:54 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Start Stop Move/Electric Service Manual/Content/Additional Information

Related Links

2/7/2022 12:08:19 AM

sitecore\van.parker

12/15/2021 9:35:29 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Home/Products/IN 2021 irp stakeholder/Content/Intro Content/Related Links

Related Links

12/15/2021 9:35:33 PM

sitecore\van.parker

12/15/2021 9:35:29 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Home/Products/in 2021 irp stakeholder old/Content/Intro Content Container/Related Links

Related Links

10/6/2016 10:16:44 AM

nam\jholly1

10/6/2016 10:16:40 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Income Qualified/Neighborhood Energy Saver/Content/Multi Column Neighbor/Related Links

Related Links

10/15/2020 4:41:02 PM

sitecore\van.parker

10/12/2020 7:33:59 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Home/Products/IN 2018 IRP Stakeholder/Content/Main content container/Related Links

Related Links

5/8/2017 3:33:40 PM

NAM\JWells4

4/28/2017 2:34:56 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Products/Multifamily Programs/Content/Unused/Multi Column/Related Links

Right Related Links

7/11/2021 8:30:25 PM

sitecore\van.parker

2/14/2017 9:36:59 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Products/Online Savings Store/Content/Not Used/Right Related Links

Related Links

7/11/2021 10:39:32 PM

sitecore\van.parker

4/6/2017 10:10:50 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Products/Outdoor Lighting/Why Choose Us/Content/Multi Column/Related Links

Related Links NC PM Regulations

3/15/2021 3:35:12 PM

sitecore\van.parker

9/22/2016 9:28:52 AM

nam\tmitch3

/sitecore/content/JssPublic/Home/Home/Products/Power Manager/Content/Unused/Related Links NC PM Regulations

Related Links right sidebar

2/26/2018 8:10:18 AM

NAM\CBIcenh

2/20/2018 9:21:18 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/NC Solar Rebates/Content/Multi Column main 2018/Related Links right sidebar

Previous Related Links

8/16/2021 3:20:36 PM

sitecore\courtney.icenhour

8/16/2021 3:12:37 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/NC GreenPower/Content/Previous Related Links

Previous Related Links

8/16/2021 4:33:16 PM

sitecore\courtney.icenhour

8/16/2021 4:32:38 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Previous Related Links

Previous Related Programs KYOHIN

8/16/2021 4:33:18 PM

sitecore\courtney.icenhour

8/16/2021 4:32:28 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Previous Related Programs KYOHIN

IN Annual

9/13/2016 6:39:06 PM

nam\jholly1

9/13/2016 6:39:01 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/IN/IN Annual

KY Annual

9/13/2016 6:40:38 PM

nam\jholly1

9/13/2016 6:40:23 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/KY/KY Annual

OH Annual

9/13/2016 6:40:51 PM

nam\jholly1

9/13/2016 6:40:43 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/OH/OH Annual

Related Links

4/1/2021 1:38:35 AM

sitecore\van.parker

8/1/2016 1:51:51 PM

NAM\AValaka

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/Generate Your Own/Content/Unused/Related Links

RL Related links

4/10/2017 9:51:20 AM

nam\rvp0696

3/3/2017 1:03:24 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/East End Replacement Project/Content/MC Main Copy/RL Related links

RL Gas Main Replacement

9/26/2016 11:14:13 AM

NAM\KMcCart

6/7/2016 4:59:35 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Home/Natural Gas/Gas Main Replacement/Content/RL Gas Main Replacement

RLinks Proj Street List

11/16/2019 2:17:39 PM

sitecore\admin

7/7/2016 1:16:22 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Home/Natural Gas/Gas Main Replacement/Content/RLinks Proj Street List

Related Links

3/24/2017 4:12:44 PM

nam\rvp0696

2/27/2017 12:47:26 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links

RL Gas line integrity

11/16/2019 2:17:40 PM

sitecore\admin

2/27/2017 12:42:01 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links/RL Gas line integrity

RL Gas line integrity

11/16/2019 2:17:40 PM

sitecore\admin

2/27/2017 12:37:49 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links/RL Gas line integrity

Related Links right col

1/6/2020 8:37:36 PM

sitecore\Van.Parker

12/30/2019 5:15:13 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/4th Street Replacement/Content/Main Content Container/Related Links right col

Related Links

12/16/2020 9:19:37 PM

sitecore\courtney.icenhour

1/16/2020 2:37:58 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Delhi Township Infrastructure/Content/Multi Column main/Related Links

Related Links

1/16/2020 3:52:13 PM

sitecore\Van.Parker

1/16/2020 2:37:58 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Roundbottom Road/Content/Main Content Container/Related Links

Neighborhood Maps East

11/16/2019 2:54:54 PM

sitecore\admin

10/10/2019 9:59:05 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Central Corridor Pipeline 2/Content/Map Content East/Neighborhood Maps East

Neighborhood Maps West

11/16/2019 2:54:55 PM

sitecore\admin

10/10/2019 10:56:48 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Central Corridor Pipeline 2/Content/Map Content West/Neighborhood Maps West

Related Links

3/24/2021 1:26:54 PM

sitecore\van.parker

2/5/2019 11:16:47 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Line A Replacement/Content/Main Content/Related Links

Related Links

3/25/2021 1:36:48 PM

sitecore\van.parker

2/5/2019 11:17:37 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Winton Rd Replacement Project/Content/Multi Column New/Related Links

Related Links

10/29/2019 3:52:31 PM

NAM\CBIcenh

10/25/2019 12:16:29 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Boone County Pipeline/Content/MC main/Related Links

Related Links

2/17/2022 6:46:27 PM

sitecore\william.natta

2/9/2022 9:40:36 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Butler County Upgrade Project/Content/MC Body/Related Links

Related Links

3/18/2022 3:13:44 PM

sitecore\william.natta

3/11/2022 1:39:25 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Milford Replacement Project/Content/MC Body/Related Links

Neighborhood Maps West

1/14/2020 8:46:05 PM

sitecore\Van.Parker

10/10/2019 10:56:48 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Central Corridor Pipeline Ext/Content/Map Content West/Neighborhood Maps West

Letters of Support

7/26/2017 11:23:06 AM

nam\rvp0696

7/20/2017 4:49:09 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Central Corridor Pipeline Ext/Project Summary/Content/Multi Column/Letters of Support

Related Links pod 1 right

8/18/2016 1:35:20 PM

default\Anonymous

6/16/2016 10:55:20 AM

nam\avalaka

/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/pod 1 cleaner/Multi Column pod 1/Related Links pod 1 right

Related Links right 2

7/15/2016 2:07:53 PM

NAM\SHaberm

6/17/2016 1:20:48 PM

nam\avalaka

/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/pod item 2 more reliable/Multi Column 1/Related Links right 2

Related Links right 3

7/15/2016 2:07:56 PM

NAM\SHaberm

6/17/2016 1:46:44 PM

nam\avalaka

/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/Pod 3/Multi Column pod 3/Related Links right 3

Related Links IN OH KY

2/25/2021 3:53:21 PM

sitecore\courtney.icenhour

11/9/2016 6:08:16 PM

NAM\cball

/sitecore/content/JssPublic/Home/Business/Billing/Paperless/Large Business Paperless/Content/Related Links IN OH KY

Related Links DEC DEP NC SC FL

2/25/2021 3:53:25 PM

sitecore\courtney.icenhour

2/25/2021 3:47:54 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Billing/Paperless/Large Business Paperless/Content/Related Links DEC DEP NC SC FL

Related Links

11/16/2019 2:18:28 PM

sitecore\admin

8/24/2016 3:18:55 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/South Carolina Rate Case/Content/Related Links

Related Links

7/15/2016 3:15:37 PM

NAM\SHaberm

5/23/2016 4:56:23 PM

nam\LPritch

/sitecore/content/JssPublic/Home/Business/Billing/Summary Billing/Sample Agreement/Content/Related Links

TOU Related Links

7/26/2016 1:47:39 PM

sitecore\Anonymous

5/20/2016 11:19:39 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/Content/Multi Column/TOU Related Links

Related Links Right

9/2/2016 3:18:12 PM

NAM\JWells4

9/2/2016 3:18:02 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/How it Works/Content/Related Links Right

FAQ Related Links

9/2/2016 3:17:14 PM

NAM\JWells4

9/2/2016 3:17:02 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/Eligibility and FAQs/Content/FAQ Related Links

Related Links right

9/2/2016 3:19:41 PM

NAM\JWells4

9/2/2016 3:19:22 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/Energy Saving Tips/Content/Energy Savings Multi Column/Related Links right

Related Links

11/16/2019 2:18:37 PM

sitecore\admin

8/16/2017 10:48:24 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Electric Fuel Adjustments/Content/Related Links

Related Links

11/16/2019 2:18:42 PM

sitecore\admin

8/24/2016 3:18:23 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/Content/Related Links

Related Links

11/16/2019 2:18:43 PM

sitecore\admin

8/24/2016 3:18:38 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/How It Works/Content/Related Links

Related Links

11/16/2019 2:18:44 PM

sitecore\admin

8/24/2016 3:18:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/Energy Saving Tips/Content/Related Links

Related Links

11/16/2019 2:18:45 PM

sitecore\admin

8/24/2016 3:18:43 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/Rate Comparison/Content/Related Links

Additional Information

8/1/2016 3:59:29 PM

nam\RBGilbe

6/27/2016 2:36:06 PM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Business/Start Stop Move/Electric Service Manual/Content/Additional Information

Related Links

8/22/2016 3:09:15 PM

sitecore\Anonymous

8/19/2016 1:55:30 PM

nam\c81423

/sitecore/content/JssPublic/Home/Business/Products/Backup Generator Program/Content/Multi Column/Related Links

Related Links Resource Docs NC

6/24/2021 6:26:42 PM

sitecore\courtney.icenhour

6/24/2021 6:21:16 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Demand Response Automation/Content/Related Links Resource Docs NC

Related Links Resource Docs SC

6/24/2021 6:27:54 PM

sitecore\courtney.icenhour

6/24/2021 6:21:16 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Demand Response Automation/Content/Related Links Resource Docs SC

Related Links

9/11/2020 5:38:28 PM

sitecore\jerry.wells

5/25/2018 10:53:55 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/Design Assistance/Content/Multi Column/Related Links

Related Links IN

6/29/2021 7:15:08 PM

sitecore\courtney.icenhour

6/29/2021 7:05:44 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Electric Essential Use/Content/Related Links IN

Related Links KY OH

6/29/2021 7:15:13 PM

sitecore\courtney.icenhour

6/29/2021 7:05:44 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Electric Essential Use/Content/Related Links KY OH

Related Links

4/3/2017 11:26:16 AM

nam\rvp0696

3/30/2017 4:19:02 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Business/Products/Energy Assessments/Content/Related Links

Related Links sidebar right

3/5/2020 3:52:32 PM

sitecore\jerry.wells

1/10/2020 6:56:26 PM

sitecore\Jerry.Wells

/sitecore/content/JssPublic/Home/Business/Products/Energy Assessments/Content/Multi Column New/Related Links sidebar right

Copy of Resource Documents

9/28/2016 10:03:27 PM

NAM\cball

9/28/2016 10:03:20 PM

NAM\cball

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/Content/Copy of Resource Documents

Copy of Related Links

7/15/2016 3:45:14 PM

NAM\SHaberm

5/24/2016 10:46:32 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/Content/Copy of Related Links

Estimate RL

9/10/2016 7:15:39 PM

nam\jholly1

5/26/2016 9:08:51 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/EEB Custom Incentive Program/Content/Estimate RL

Resource RL

9/10/2016 7:24:14 PM

nam\jholly1

9/10/2016 7:24:05 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/EEB Custom Incentive Program/Content/Resource RL

Resource Documents

7/15/2016 3:47:58 PM

NAM\SHaberm

5/26/2016 3:41:58 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/Technical Assistance/Content/Resource Documents

Learn More RL

4/29/2019 2:50:38 PM

NAM\JWells4

4/29/2019 2:18:30 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/EnergyWise Business/Content/Learn More RL

Related Links DEC

6/30/2021 3:13:40 PM

sitecore\courtney.icenhour

6/30/2021 2:44:12 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/EnergyWise Business/Content/Related Links DEC

Related Links DEP

6/30/2021 3:13:44 PM

sitecore\courtney.icenhour

6/30/2021 2:44:12 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/EnergyWise Business/Content/Related Links DEP

Related Links 1

7/1/2021 2:56:57 AM

sitecore\courtney.icenhour

5/24/2016 1:42:34 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Business/Products/Outdoor Lighting/Content/NOT USED/Related Links 1

Related Links

7/29/2016 10:19:04 AM

sitecore\Anonymous

6/5/2016 9:37:29 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/Outdoor Lighting/Why Choose Us/Content/Related Links

Learn More

7/1/2021 1:47:26 PM

sitecore\courtney.icenhour

7/1/2021 1:46:34 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Power Manager for Business/Content/Multi Column/Learn More

Learn More RL DEC NC

6/16/2021 4:04:22 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL DEC NC

Learn More RL DEC SC

6/16/2021 4:04:29 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL DEC SC

Learn More RL IN

6/16/2021 4:04:12 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL IN

Learn More RL KY

6/16/2021 4:04:16 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL KY

Related Links

8/1/2016 3:31:55 PM

nam\LPritch

6/28/2016 2:35:10 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/Renewables/NC GreenPower/Content/Related Links

Right Related Links

9/14/2016 9:28:01 AM

nam\jholly1

9/14/2016 9:27:55 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/NC GreenPower/Content/Content from Home/Right Related Links

IN Annual

9/13/2016 2:46:25 PM

nam\jholly1

9/13/2016 1:51:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/GoGreen Energy/Content/IN/IN Annual

KY Annual

9/13/2016 2:41:10 PM

nam\jholly1

9/13/2016 1:51:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/GoGreen Energy/Content/KY/KY Annual

OH Annual

9/13/2016 2:41:26 PM

nam\jholly1

9/13/2016 1:51:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/GoGreen Energy/Content/OH/OH Annual

X Related Links

1/26/2022 4:36:29 AM

sitecore\courtney.icenhour

7/27/2016 8:35:59 PM

nam\DMcGuir

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own/Content/X Related Links

Related Links

12/7/2021 12:52:33 PM

sitecore\jerry.wells

7/27/2016 8:35:59 PM

nam\DMcGuir

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own BACKUP COPY/Content/Related Links

Related Links Application forms

2/8/2022 7:33:34 PM

sitecore\jerry.wells

2/4/2022 2:43:18 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links Application forms

Related Links General Info

2/8/2022 7:33:59 PM

sitecore\jerry.wells

1/31/2022 7:55:05 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links General Info

Related Links Resources

2/8/2022 7:34:22 PM

sitecore\jerry.wells

2/4/2022 2:41:52 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links Resources

Related Links Where to Apply

2/8/2022 7:34:40 PM

sitecore\jerry.wells

1/31/2022 8:01:08 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links Where to Apply

Related Links Application forms

2/8/2022 7:37:20 PM

sitecore\jerry.wells

2/4/2022 2:43:18 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links Application forms

Related Links General Info

2/8/2022 7:37:37 PM

sitecore\jerry.wells

1/31/2022 7:55:05 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links General Info

Related Links Resources

2/8/2022 7:38:03 PM

sitecore\jerry.wells

2/4/2022 2:41:52 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links Resources

Related Links Where to Apply

2/8/2022 7:38:32 PM

sitecore\jerry.wells

1/31/2022 8:01:08 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links Where to Apply

Related Links Application forms

2/8/2022 7:39:29 PM

sitecore\jerry.wells

2/4/2022 2:43:18 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links Application forms

Related Links General Info

2/8/2022 7:39:54 PM

sitecore\jerry.wells

1/31/2022 7:55:05 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links General Info

Related Links Resources

2/8/2022 7:40:14 PM

sitecore\jerry.wells

2/4/2022 2:41:52 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links Resources

Related Links Where to Apply

2/8/2022 7:40:37 PM

sitecore\jerry.wells

1/31/2022 8:01:08 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links Where to Apply

Related Links 1

3/21/2020 1:23:04 AM

sitecore\jerry.wells

2/4/2020 8:35:31 PM

sitecore\Jerry.Wells

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/Related Links 1

Related Links

1/17/2018 9:12:31 AM

NAM\CBIcenh

1/16/2018 10:03:08 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/Related Links

Related Links 2

1/19/2018 2:58:11 PM

NAM\CBIcenh

1/19/2018 8:34:22 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/Related Links 2

Related Links right

2/6/2020 6:08:47 PM

sitecore\Jerry.Wells

2/6/2020 6:08:42 PM

sitecore\Jerry.Wells

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators IN DEC DEP/Custom/Multi Column classic/Related Links right

Related Links NC SC IN

7/1/2021 1:37:00 PM

sitecore\jerry.wells

7/1/2021 1:13:09 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators IN DEC DEP/Performance/Multi Column performance/Related Links NC SC IN

Related Links OH KY

1/24/2020 8:50:00 PM

sitecore\Janet.Kosoglov

1/24/2020 7:22:25 PM

sitecore\Janet.Kosoglov

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators KY/Custom OH KY/Multi Column classic OH KY/Related Links OH KY

Estimate incentives

1/3/2017 7:11:46 AM

NAM\JWells4

12/27/2016 9:07:34 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Performance/Content/Multi Column/Estimate incentives

Resources

1/3/2017 7:11:30 AM

NAM\JWells4

12/27/2016 8:58:51 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Performance/Content/Multi Column/Resources

RL Custom Incentives

11/23/2020 3:18:32 PM

sitecore\jerry.wells

12/13/2018 9:57:43 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives FL/Content/Multi Column NoCache/RL Custom Incentives

Related Links

7/15/2016 3:58:55 PM

NAM\SHaberm

5/16/2016 8:24:23 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Natural Gas/Gas Main Replacement/Content/Related Links

Related Links KY

5/4/2021 9:40:45 PM

sitecore\van.parker

3/4/2021 1:23:14 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop/Strikestop Monetary Coverage/Content/Multi Column Strikestop/Related Links KY

Related Links OH

5/4/2021 9:40:45 PM

sitecore\van.parker

3/4/2021 12:53:15 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop/Strikestop Monetary Coverage/Content/Multi Column Strikestop/Related Links OH

Related Links KY

3/4/2021 1:23:16 AM

sitecore\janet.kosoglov

3/4/2021 1:23:14 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop Orig/Content/Multi Column Strikestop/Related Links KY

Related Links OH

3/4/2021 1:23:00 AM

sitecore\janet.kosoglov

3/4/2021 12:53:15 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop Orig/Content/Multi Column Strikestop/Related Links OH

Related Links

3/19/2021 5:53:36 PM

sitecore\janet.kosoglov

5/17/2016 3:13:00 PM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Home Services/Underground Protection/Content/Related Links

Related links

4/8/2021 1:38:17 PM

sitecore\van.parker

3/28/2021 10:35:01 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/About Us/IRP DEK/Content/Related links

Right Related Links

5/22/2017 1:38:27 PM

NAM\JWells4

5/22/2017 9:46:04 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Businesses/Renewable Energy/Content/Multi Column Renewable Energy/Right Related Links

Related Links

7/15/2016 4:19:27 PM

NAM\SHaberm

4/28/2016 4:54:32 PM

nam\LPritch

/sitecore/content/JssPublic/Home/Our Company/About Us/Coal Plant Decommissioning Program/Content/MultiColumn/Related Links

Related Links

12/3/2020 2:01:49 PM

sitecore\van.parker

12/3/2020 2:01:00 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Aero Reliability Project/Content/Main Content Container/Related Links

Related Links

5/21/2021 4:45:52 PM

sitecore\van.parker

5/21/2021 1:52:28 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Clermont/Content/Main Content Container/Related Links

Related Links

7/5/2017 4:41:30 PM

nam\rvp0696

7/5/2017 2:24:58 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Fairfax to Senco Substations/Content/Multi Column/Related Links

Related Links

7/15/2016 4:19:59 PM

NAM\SHaberm

5/5/2016 10:05:32 AM

nam\LPritch

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Morgan Road/Content/MultiColumn/Related Links

Related Links

12/20/2017 2:11:41 PM

nam\rvp0696

12/19/2017 9:19:53 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Warren to Nickel Project/Content/Main Copy MC/Related Links

Related Links

6/20/2018 3:18:02 PM

nam\rvp0696

6/20/2018 3:09:10 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Ethics/Content/Multi Column/Related Links

Related Links

7/15/2016 4:20:27 PM

NAM\SHaberm

3/29/2016 9:59:43 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Content/MultiColumn/Related Links

Related Links

7/15/2016 4:20:28 PM

NAM\SHaberm

3/29/2016 10:20:30 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Economic Benefits/Content/Multi Column/Related Links

Related Links

7/15/2016 4:20:29 PM

NAM\SHaberm

3/29/2016 9:59:43 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Environmental Impact/Content/MultiColumn/Related Links

Related Links

7/15/2016 4:20:32 PM

NAM\SHaberm

3/29/2016 10:20:30 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Nuclear Regulatory Process/Content/MultiColumn/Related Links

Related Links

9/9/2016 8:46:01 PM

nam\jholly1

9/8/2016 10:43:41 AM

NAM\LBorder

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/FAQ/Content/Multi Column/Related Links

Related Links

9/9/2016 1:28:10 PM

default\Anonymous

9/1/2016 9:48:42 AM

nam\DMcGuir

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Natural Gas/FAQ/Content/Related Links

Related Links

9/27/2018 5:29:07 PM

nam\rvp0696

5/23/2018 9:05:08 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Natural Gas/W S Lee Station/Content/MC Main Content/Related Links

Safe Basin Closure Related Links

10/12/2016 3:46:41 PM

nam\rvp0696

10/12/2016 3:46:33 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Content/PDP Our Plans/SafeBasinClosure/Safe Basin Closure Related Links

Idpd Experts Related Links

7/15/2016 4:16:24 PM

NAM\SHaberm

5/11/2016 9:52:16 AM

nam\tmitch3

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Content/PDP Our Plans/IndependentExperts/Idpd Experts Related Links

Safe Recycling Related Links

10/26/2016 11:09:24 AM

NAM\RBGilbe

10/26/2016 10:46:26 AM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Content/PDP Our Plans/Safe Recycling/Safe Recycling Related Links

Safe Basin Closure Related Links

2/8/2017 8:22:55 AM

NAM\JWells4

10/12/2016 3:46:33 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Safe Basin Closure/Content/Safe Basin Closure Related Links

Safe Recycling Related Links

2/8/2017 8:21:54 AM

NAM\JWells4

10/26/2016 10:46:26 AM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Safe Recycling/Content/Safe Recycling Related Links

Groundwater Related Links

6/20/2018 10:05:04 AM

NAM\CBIcenh

6/20/2018 9:48:55 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Groundwater Studies/Content/Multi Column/Groundwater Related Links

Related Links new

11/6/2017 1:02:27 PM

nam\c81423

11/3/2017 9:33:56 AM

nam\c81423

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Groundwater Studies/2015 Groundwater Studies/Content/Multi Column New/Related Links new

Idpd Experts Related Links

2/8/2017 8:21:29 AM

NAM\JWells4

1/24/2017 12:10:08 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Independent Experts/Content/Idpd Experts Related Links

Related Links Media

11/16/2019 2:55:39 PM

sitecore\admin

2/3/2017 8:49:18 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Commitment Plant Communities/Content/Multi Column/Related Links Media

Related Links Research

11/16/2019 2:55:40 PM

sitecore\admin

2/3/2017 8:42:01 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Commitment Plant Communities/Content/Multi Column/Related Links Research

Related Links

10/20/2017 3:42:26 PM

nam\rvp0696

10/20/2017 3:20:26 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Water Plans/Content/Related Links

Related Links

7/15/2016 4:18:31 PM

NAM\SHaberm

3/30/2016 4:34:37 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Rogers Energy Complex/Content/Related Links

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DECNC/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DECSC/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEI/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEK/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEO/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEPNC/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEPSC/RL Smart Meter

Related Links

12/14/2016 10:01:00 AM

NAM\JWells4

11/15/2016 2:45:59 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Grid Modernization FAQs/Content/Related Links

Related Links

7/14/2017 6:03:40 PM

nam\rvp0696

7/14/2017 5:57:02 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Coalition/Content/Multi Column/Related Links

Contact Us RL

7/15/2016 4:25:42 PM

NAM\SHaberm

4/25/2016 5:53:32 PM

nam\LPritch

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Coalition/Content/Multi Column/Contact Us RL

Related links

5/25/2018 9:43:20 AM

nam\rvp0696

5/17/2018 3:11:05 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Careers/Customer Service/Content/Related links

RL Video Links

8/31/2017 12:43:24 PM

nam\rvp0696

8/30/2017 4:56:25 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Cincinnati/Content/Multi Column/RL Video Links

Related Links

8/23/2016 2:51:43 PM

sitecore\Anonymous

3/30/2016 6:59:37 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Content/MultiColumn/Related Links

Related Links

7/15/2016 4:31:17 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Selective Catalytic Reduction/Content/Related Links

Related Links

7/15/2016 4:31:21 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Sulfur Dioxide Scrubbers/Content/Related Links

Related Links

7/15/2016 4:31:23 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Particulate Control Technologies/Content/Related Links

Copy of Related Links

7/15/2016 4:31:23 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Mercury Control Technologies/Content/Copy of Related Links

Related Links

7/15/2016 4:35:28 PM

NAM\SHaberm

3/31/2016 4:49:38 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Conservation Stewardship/Peregrine Falcons/Content/Related Links

Related Links

7/15/2016 4:35:41 PM

NAM\SHaberm

3/31/2016 4:54:56 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Conservation Stewardship/Eagles/Content/Related Links

Related Links

4/26/2020 8:01:18 PM

sitecore\van.parker

6/11/2019 8:04:28 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Environment/Global Climate Change/Content/Intro Content Container/Related Links

Climate Reports

4/27/2020 9:13:34 PM

sitecore\van.parker

4/26/2020 7:59:35 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/Environment/Global Climate Change/Content/Intro Content Container/Climate Reports

Related Links CDP

8/10/2016 1:39:13 PM

NAM\EYoung2

8/9/2016 1:14:08 PM

NAM\LBorder

/sitecore/content/JssPublic/Home/Our Company/Environment/Global Climate Change/Carbon Disclosure Project/Content/Related Links CDP

RL Additional Solar Projects DECDEPOHKY

1/20/2022 2:28:02 PM

sitecore\janet.kosoglov

1/20/2022 2:27:45 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/RL Additional Solar Projects DECDEPOHKY

Related Links ALL

7/12/2021 5:16:46 PM

sitecore\william.natta

7/12/2021 5:14:37 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/Related Links ALL

Related Links DEF

7/12/2021 5:17:15 PM

sitecore\william.natta

5/12/2017 1:49:51 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/Related Links DEF

Related Links DEI

7/12/2021 5:16:40 PM

sitecore\william.natta

5/12/2017 1:49:51 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/Related Links DEI

RL NC

1/20/2022 2:11:28 AM

sitecore\janet.kosoglov

7/12/2021 5:06:24 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/RL NC

RL SC

1/20/2022 2:11:43 AM

sitecore\janet.kosoglov

1/20/2022 2:11:43 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/RL SC

Related Links DECSC

7/12/2021 5:37:10 PM

sitecore\william.natta

9/16/2016 3:11:18 PM

nam\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/SC Solar Energy Programs/Solar 101/Content/Related Links DECSC

Related Links DEPSC

7/12/2021 5:37:57 PM

sitecore\william.natta

9/16/2016 3:11:18 PM

nam\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/SC Solar Energy Programs/Solar 101/Content/Related Links DEPSC

Related Links

11/16/2019 2:55:43 PM

sitecore\admin

9/9/2019 9:21:50 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Solar Power in NC/Content/Related Links

Related Links

3/12/2018 4:44:39 PM

nam\rvp0696

3/12/2018 4:13:47 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Corporate Governance/Political Expenditures Policy/Content/Related Links

RL DukePAC Contributions

3/12/2018 4:05:01 PM

nam\rvp0696

8/28/2017 5:32:38 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Corporate Governance/Political Expenditures Policy/Content/RL DukePAC Contributions

Related Links Fin

8/19/2019 8:07:44 AM

nam\rvp0696

9/7/2016 1:41:51 PM

NAM\cball

/sitecore/content/JssPublic/Home/Our Company/Investors/Financials/Content/Related Links Fin

Left col

8/28/2019 3:25:03 PM

nam\rvp0696

8/19/2019 8:10:24 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Financials/Content/SEC Filings by company/Left col

Right col

8/28/2019 3:25:00 PM

nam\rvp0696

8/19/2019 8:10:24 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Financials/Content/SEC Filings by company/Right col

Green Bond Projects

3/15/2022 1:45:59 AM

sitecore\van.parker

3/12/2022 5:24:42 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/DEF Sustainable Financing/Content/Section 1 Container/Green Bond Projects

Copy of Green Bond Projects

3/12/2022 5:55:16 PM

sitecore\van.parker

3/12/2022 5:24:42 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/DEF Sustainable Financing/Content/Section 2 Container/Copy of Green Bond Projects

Unapproved projects

1/9/2022 5:36:26 PM

sitecore\van.parker

8/13/2019 3:05:53 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects/Content/Unapproved projects

DEC Projects

9/24/2019 12:11:18 PM

nam\rvp0696

8/13/2019 3:05:53 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects/Content/Main Content Container/DEC Projects

DEP Projects

1/9/2022 5:37:12 PM

sitecore\van.parker

8/13/2019 3:18:17 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects/Content/Main Content Container/DEP Projects

DEC Projects

9/15/2020 5:19:22 PM

sitecore\van.parker

8/13/2019 3:05:53 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects DEF/Content/Main Content Container/DEC Projects

Related Links

11/16/2019 2:21:23 PM

sitecore\admin

7/1/2016 3:14:20 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Our Company/Investors/Individual Investors/Shareholder Services/Content/Related Links

Link to First Letter

7/20/2021 9:37:26 PM

sitecore\van.parker

5/19/2021 12:39:21 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Investors/Response to Elliott Management/Content/Multi Column New/Link to First Letter

Related Links Stakeholder

7/20/2021 1:37:06 PM

sitecore\van.parker

5/19/2021 12:39:21 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Investors/Response to Elliott Management/Content/Multi Column New/Related Links Stakeholder

Related Links

5/21/2021 3:33:03 PM

sitecore\janet.kosoglov

5/21/2021 3:32:59 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Investors/Response to Elliott Management/Content/Multi Column New/Related Links

RL Video Links

10/27/2017 6:21:39 PM

nam\rvp0696

8/30/2017 4:56:25 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/KYFuture/Content/Multi Column/RL Video Links

Related Links Contacts

11/14/2019 10:57:24 AM

NAM\WNatta

10/11/2019 8:54:56 AM

NAM\WNatta

/sitecore/content/JssPublic/Home/Partner With Us/Economic Development/Indiana/Rates and Incentives/Content/Related Links Contacts

Related Links Sidebar

7/14/2017 9:25:24 AM

NAM\JWells4

3/31/2016 1:37:06 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Partner With Us/Economic Development/Indiana/Rates and Incentives/Content/Multi Column/Related Links Sidebar

Related Links Sidebar

3/16/2022 2:02:32 PM

sitecore\william.natta

3/31/2016 1:22:56 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Partner With Us/Economic Development/Indiana/Site Readiness Program/Content/Related Links Sidebar

Related Links

11/16/2019 2:22:07 PM

sitecore\admin

5/20/2016 1:34:20 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Partner With Us/Retail Electric and Gas Sup/Gas/Firm Transportation/Content/Related Links

Contact Us

7/15/2016 4:59:12 PM

NAM\SHaberm

3/31/2016 9:17:44 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Partner With Us/Suppliers/Supplier Diversity/Content/MC Header area/Contact Us

Related Links

7/15/2016 4:59:13 PM

NAM\SHaberm

3/31/2016 9:13:39 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Partner With Us/Suppliers/Supplier Diversity/Content/MC Header area/Related Links

RL Brand Requirements

1/28/2022 2:46:54 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Brand Requirements/RL Brand Requirements

RL HVAC

1/28/2022 2:46:45 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCN HVAC Lighting/RL HVAC

RL Lighting

1/28/2022 2:46:31 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCN HVAC Lighting/RL Lighting

RL Smart Saver Worksheets

1/28/2022 2:46:09 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Smart Saver Worksheets/RL Smart Saver Worksheets

RL Marketing Collateral

1/28/2022 2:45:30 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Marketing Collateral/RL Marketing Collateral

Related Documents

2/6/2017 3:25:26 PM

NAM\JWells4

2/2/2017 11:46:49 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Nuclear Power/Content/Related Documents

Related Links

7/15/2016 5:21:28 PM

NAM\SHaberm

4/5/2016 8:40:07 AM

nam\c81423

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Energy From Coal/Content/Related Links

Related Links

7/15/2016 5:21:38 PM

NAM\SHaberm

4/5/2016 8:40:07 AM

nam\c81423

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Pumped Storage Hydro Plants/Content/Related Links

Related Links

7/15/2016 5:21:48 PM

NAM\SHaberm

4/5/2016 8:40:07 AM

nam\c81423

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Conventional Hydro Plants/Content/Related Links

Related Links

12/1/2016 8:52:32 AM

nam\jholly1

10/26/2016 11:30:40 AM

NAM\LAFrink

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Delivering Electricity/Content/Related Links

Related Links NC SC IN KY

10/8/2020 8:55:03 PM

sitecore\janet.kosoglov

9/25/2020 8:42:50 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Energy Education/Energy Centers and Programs/Energy Revolution Energy Efficiency in Schools/energy efficiency kit/Content/Multi Column NC SC KY IN/Related Links NC SC IN KY

Related Links Right Sidebar

6/7/2018 10:34:34 AM

NAM\c81423

3/24/2016 10:18:53 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Content/Related Links Right Sidebar

External Related Links

7/15/2016 5:33:35 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/External Related Links

Internal Related Links

7/15/2016 5:33:35 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/Internal Related Links

Related Links

11/16/2019 2:22:37 PM

sitecore\admin

3/24/2016 1:54:34 PM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/Accordion Industry Collab/Related Links

Copy of External Related Links

7/15/2016 5:33:38 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/Copy of External Related Links

External Related Links

7/15/2016 5:33:39 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/External Related Links

Internal Related Links

7/15/2016 5:33:47 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/Internal Related Links

External Related Links

7/15/2016 5:33:53 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Charging Your PEV/Content/External Related Links

Internal Related Links

7/15/2016 5:33:53 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Charging Your PEV/Content/Internal Related Links

External Related Links

7/15/2016 5:33:55 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/External Related Links

Internal Related Links

7/15/2016 5:33:55 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/Internal Related Links

Related Links

11/18/2016 1:06:17 PM

NAM\KMcCart

11/18/2016 1:05:23 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/Related Links

External Related Links

6/7/2018 10:03:41 AM

NAM\c81423

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/History of EVs/Content/Multi Column History of PEVs/External Related Links

Internal Related Links

6/7/2018 10:03:45 AM

NAM\c81423

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/History of EVs/Content/Multi Column History of PEVs/Internal Related Links

External Related Links

6/7/2018 11:37:58 AM

NAM\c81423

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/FAQ/Content/Multi Column/External Related Links

Internal Related Links

6/7/2018 11:37:44 AM

NAM\c81423

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/FAQ/Content/Multi Column/Internal Related Links

Related Links 1

2/10/2022 9:56:48 PM

sitecore\janet.kosoglov

2/8/2022 10:45:16 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Funding Guidelines/Content/Related Links 1

Related Links

4/3/2017 11:24:47 AM

NAM\RBGilbe

3/21/2017 2:30:44 PM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Community Initiatives/Charlotte Community Affairs/Content/Related Links

Copy of Related Links

11/16/2021 12:46:25 AM

sitecore\janet.kosoglov

6/30/2020 8:38:46 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Content/Multi Column New/Copy of Related Links

Related Links

3/29/2021 12:09:51 AM

sitecore\janet.kosoglov

6/30/2020 8:38:46 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Hometown OLD/Content/Multi Column NoCache/Related Links

Related Links

2/16/2022 7:14:41 PM

sitecore\janet.kosoglov

2/16/2022 7:12:51 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Hometown/Content/Multi Column/Related Links

Copy of Related Links

11/16/2021 12:48:08 AM

sitecore\janet.kosoglov

6/30/2020 8:38:46 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/South Carolina/Content/Multi Column New/Copy of Related Links

Related Links

3/3/2022 2:35:42 PM

sitecore\janet.kosoglov

2/25/2022 4:06:00 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/SC Storm Resiliency/Content/Related Links

Copy of Related Links

11/16/2021 12:44:06 AM

sitecore\janet.kosoglov

5/3/2019 12:26:29 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Florida/Content/Multi Column New/Copy of Related Links

Related Links

11/16/2021 12:42:40 AM

sitecore\janet.kosoglov

1/20/2020 6:12:18 PM

sitecore\Janet.Kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Ohio/Content/Multi Column New/Related Links

Copy of Related Links

11/17/2021 3:38:25 PM

sitecore\janet.kosoglov

11/17/2021 3:38:21 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Kentucky/Content/Multi Column New/Copy of Related Links

Copy of Related Links

11/17/2021 3:37:36 PM

sitecore\janet.kosoglov

11/17/2021 3:37:35 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Indiana/Content/Multi Column New/Copy of Related Links

Related Links

1/11/2019 1:44:34 PM

NAM\c81423

1/3/2019 9:38:31 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Online Application/Content/Related Links

RelatedLinks CountyList SHARED

10/11/2018 12:51:17 PM

NAM\c81423

10/5/2018 11:48:36 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/K 12 Education/Content/RelatedLinks CountyList SHARED

Related Links

5/22/2019 10:50:41 AM

NAM\c81423

5/3/2019 11:54:08 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/K 12 Education/Content/Multi Column New/Related Links

Related Links SHARED

5/22/2019 10:51:11 AM

NAM\c81423

5/3/2019 11:43:09 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Nature/Content/Multi Column New/Related Links SHARED

Related Links

5/22/2019 10:51:34 AM

NAM\c81423

5/3/2019 12:15:56 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Workforce Development/Content/Multi Column New/Related Links

Related Links

5/22/2019 10:52:06 AM

NAM\c81423

5/3/2019 12:16:40 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Local Impact/Content/Multi Column New/Related Links

Related Links Sidebar

1/11/2021 8:05:56 PM

sitecore\janet.kosoglov

6/2/2020 6:38:13 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Catawba/Content/Multi Column/Related Links Sidebar

Final Agreement

1/11/2021 8:05:56 PM

sitecore\janet.kosoglov

10/18/2018 9:39:45 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Catawba/Content/Multi Column/Final Agreement

Related Links 1

7/15/2016 5:59:16 PM

NAM\SHaberm

3/28/2016 3:05:42 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Keowee Toxaway/Content/Related Links 1

Related Links

7/15/2016 5:59:28 PM

NAM\SHaberm

3/28/2016 3:17:27 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Keowee Toxaway/Relicensing/Content/Related Links

Attachments

7/15/2016 5:59:28 PM

NAM\SHaberm

3/28/2016 3:26:44 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Keowee Toxaway/Final License Application/Content/Attachments

Related Links 1

5/19/2020 5:10:43 PM

sitecore\janet.kosoglov

5/13/2020 2:02:15 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Content/Lake Services PDP/Catawba/Multi Column/Related Links 1

Related Links

1/11/2021 8:00:12 PM

sitecore\janet.kosoglov

10/24/2016 12:42:03 PM

nam\c81423

/sitecore/content/JssPublic/Home/Community/Lakes/Services/CW Shoreline Management Plan/Content/Long Copy MC/Related Links

Related Links

4/22/2020 3:42:42 PM

sitecore\janet.kosoglov

5/6/2016 2:10:40 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Services/CWHEP/Content/Multi Column/Related Links

Related Links Micro

1/14/2021 9:28:45 PM

sitecore\janet.kosoglov

12/15/2016 12:30:39 PM

nam\c81423

/sitecore/content/JssPublic/Home/Community/Lakes/Services/KT Shoreline Management Plan/Content/Related Links Micro

Related Links micro

7/15/2016 6:01:47 PM

NAM\SHaberm

3/31/2016 4:01:27 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Services/KT Shoreline Management Plan/Executive Summary/Content/Related Links micro

Related Links

6/3/2021 2:20:41 PM

sitecore\van.parker

5/6/2016 2:54:30 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Services/KTHEP/Content/Multi Column/Related Links

Related Links

7/15/2021 12:34:34 AM

sitecore\janet.kosoglov

7/13/2021 2:15:32 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Yadkin Pee Dee/Lake Tillery Neighbors/Content/Multi Column New/Related Links

Related Links

10/15/2020 6:06:03 PM

sitecore\janet.kosoglov

10/15/2020 6:05:58 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Nuisance Aquatic Plants/Content/MultiColumn/Related Links

Related Links

7/15/2016 6:02:26 PM

NAM\SHaberm

3/31/2016 4:40:21 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Cultural Resources/Content/Related Links

Related Links

7/15/2016 6:03:16 PM

NAM\SHaberm

5/6/2016 4:14:51 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Recreation Information/2015 Hydro Rec Reports/Content/Related Links

Related Links

8/18/2021 8:08:51 PM

sitecore\janet.kosoglov

8/18/2021 7:05:47 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Recreation Information/CW Recreation Enhancements/Content/Multi Column New/Related Links

Related Links

7/15/2016 6:03:35 PM

NAM\SHaberm

7/1/2016 12:41:49 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Dam And Lake Level Basics/Content/Related Links

Related Links LIP Updates

7/16/2018 11:30:57 AM

NAM\c81423

7/16/2018 9:53:21 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Catawba Wateree DMAG/Content/Related Links LIP Updates

Related Links

3/31/2017 10:45:17 AM

NAM\JWells4

3/31/2017 9:23:41 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Keowee Toxaway DMAG/Content/Related Links

Related Links

2/7/2019 10:45:05 AM

NAM\c81423

3/31/2017 9:31:25 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Yadkin Pee Dee DMAG/Content/Related Links

Related Links

2/17/2017 2:36:23 PM

nam\c81423

2/16/2017 1:45:48 PM

nam\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Content/Multi Column/Related Links

Related Links Micro

4/30/2021 5:47:47 PM

sitecore\jerry.wells

4/25/2016 9:30:17 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Distribution Lines Vegetation Management/Content/Distribution Lines MultiColumn/Related Links Micro

Related Links Micro

7/15/2016 6:05:48 PM

NAM\SHaberm

4/25/2016 9:42:13 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Transmission Lines Vegetation Management/Content/Related Links Micro

Related Links IN

11/16/2019 2:55:52 PM

sitecore\admin

4/1/2019 4:24:38 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links IN

Related Links KY

11/16/2019 2:55:52 PM

sitecore\admin

4/3/2019 10:10:47 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links KY

Related Links OH

11/16/2019 2:55:52 PM

sitecore\admin

4/3/2019 10:12:59 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links OH

Related Links Micro NC SC FL

11/16/2019 2:55:53 PM

sitecore\admin

4/1/2019 4:15:11 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links Micro NC SC FL

Related Links Micro ALL

9/10/2021 7:04:16 PM

sitecore\janet.kosoglov

9/10/2021 7:04:12 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/How We Manage Rights of Way/Content/Related Links Micro ALL

Related Links Micro

7/15/2016 6:07:24 PM

NAM\SHaberm

4/25/2016 1:18:52 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Distribution Lines Guidelines and Restrictions/Content/Related Links Micro

Related Links Micro CAR

2/11/2022 6:54:40 PM

sitecore\janet.kosoglov

10/5/2021 1:56:42 AM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro CAR

Related Links Micro FL

2/11/2022 6:55:10 PM

sitecore\janet.kosoglov

2/11/2022 6:55:09 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro FL

Related Links Micro MW

2/11/2022 6:55:35 PM

sitecore\janet.kosoglov

2/11/2022 6:55:34 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro MW

Related Links Micro

7/15/2016 6:07:40 PM

NAM\SHaberm

4/25/2016 1:14:59 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What is a Right of Way/Content/Related Links Micro

Related Links

2/8/2019 12:28:03 PM

nam\rvp0696

1/21/2019 9:46:03 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Emerald Ash Borer/Content/Intro Text Container/Related Links

NEW Related Links

5/14/2021 1:13:13 PM

sitecore\jerry.wells

9/6/2016 11:40:10 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/What is a Pipeline/Content/Multi Column/NEW Related Links

Related Links

5/14/2021 1:14:51 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Communicating with Prop Own/Content/Multi Column/Related Links

Related Links

5/14/2021 1:19:46 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Before you Plant or Build/Content/Multi Column/Related Links

Related Links

5/14/2021 1:22:12 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Identifying Pipelines/Content/Multi Column/Related Links

Related Links

5/14/2021 1:23:51 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Pipeline Construction/Content/Multi Column/Related Links

Related Links

9/12/2016 12:33:51 PM

nam\tmitch3

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Pipeline Clearance FAQ/Content/Related Links

Right Storm Related Links

11/16/2019 2:23:13 PM

sitecore\admin

10/7/2016 12:13:04 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Customer Service/Storms TESTING ONLY/Content/Right Storm Related Links

Right Storm Related Links

9/11/2017 7:08:44 PM

nam\rvp0696

10/7/2016 12:13:04 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Outages/Severe Weather/Content/Right Storm Related Links

Related Links right sidebar

11/3/2016 8:24:33 AM

NAM\CBIcenh

10/31/2016 9:55:49 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Safety and Preparedness/Contractors First Responders/Content/Multi Column/Related Links right sidebar

Related Links

10/2/2019 9:42:30 AM

nam\rvp0696

9/26/2019 8:46:25 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Safety and Preparedness/Overhead Power Lines/Content/Multi Column/Related Links

Related Links

7/15/2016 5:39:38 PM

NAM\SHaberm

5/5/2016 1:59:45 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Safety and Preparedness/Electric Safety/Content/Related Links

Related Links

12/21/2017 3:40:18 PM

NAM\CBIcenh

10/26/2017 12:54:07 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Spanish/Servicios al Hogar/StrikeStop/Content/Multi Column Strikestop/Related Links

Related Links

9/7/2016 7:40:59 PM

nam\tmitch3

9/6/2016 12:33:51 AM

nam\tmitch3

/sitecore/content/JssPublic/Home/Spanish/Facturas y pagos/Pago Automatico/Content/Related Links

Related Links

11/16/2019 2:23:47 PM

sitecore\admin

6/6/2016 4:53:44 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Spanish/Facturas y pagos/Facturacion Presupuestada/Content/Related Links

Related Links

8/13/2020 8:51:08 PM

sitecore\van.parker

5/21/2020 8:34:53 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Payment Options/Multi Column/Related Links

Related Links all but FL

6/29/2021 4:06:36 PM

sitecore\jerry.wells

5/14/2020 8:45:05 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Energy Saving Tips/Multi Column/Related Links all but FL

Related Links FL

6/29/2021 5:24:21 PM

sitecore\jerry.wells

5/14/2020 8:45:05 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Energy Saving Tips/Multi Column/Related Links FL

Related Links DEC

6/16/2021 3:18:18 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEC

Related Links DEF

6/16/2021 4:05:43 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEF

Related Links DEI

6/16/2021 4:00:32 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEI

Related Links DEK

6/16/2021 3:58:08 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEK

Related Links DEO

6/16/2021 3:55:00 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEO

Related Links DEP

6/16/2021 3:49:44 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEP

Right col helpful resources

1/19/2022 11:21:42 PM

sitecore\van.parker

12/22/2021 7:08:09 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/info/ChoiceSupplierInfo/Content/Intro copy/Right col helpful resources

Related Links

9/16/2016 2:53:46 PM

NAM\JWells4

9/16/2016 2:53:41 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/info/duke energy logo request/Content/Related Links

Copy of Related Links

11/9/2016 11:28:34 AM

nam\jholly1

11/9/2016 11:28:29 AM

nam\jholly1

/sitecore/content/JssPublic/Home/info/duke energy logo request/logo terms of use/Content/Copy of Related Links

Related Links

3/13/2019 11:18:15 AM

nam\spapai

3/13/2019 11:10:13 AM

nam\spapai

/sitecore/content/JssPublic/Home/Test/Automation/Components/MultiColumn/Content/Related Links

Related Links2

3/27/2019 9:06:01 AM

nam\kvelava

3/27/2019 8:46:30 AM

nam\kvelava

/sitecore/content/JssPublic/Home/Test/Automation/Components/MultiColumn/Content/Related Links2

Related Links

12/15/2021 7:57:55 PM

sitecore\janet.kosoglov

12/15/2021 7:57:50 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Test/Validation/Content/Related Links

Related Links

9/7/2016 2:19:09 PM

NAM\SHaberm

9/7/2016 2:03:49 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Smoke Test/josh test page/Content/Related Links

Related Links

7/25/2016 9:35:21 AM

nam\jholly1

7/22/2016 9:20:05 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Smoke Test/Training Page/Content/Related Links

Related Links

7/15/2016 6:57:35 PM

NAM\SHaberm

4/28/2016 9:09:19 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Smoke Test/Smoke Test 2/Content/Related Links

Related Links

7/14/2017 8:25:29 AM

nam\jholly1

7/14/2017 7:52:59 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Regression/Multi Column/Content/Related Links

Related Links Left Sidebar

10/14/2021 3:28:16 PM

sitecore\matthew.evanoff

10/14/2021 2:59:32 PM

sitecore\matthew.evanoff

/sitecore/content/JssPublic/Home/JSSMigration/Multi Column/Content/Related Links Left Sidebar

Related Links Right Sidebar

10/14/2021 3:28:16 PM

sitecore\matthew.evanoff

10/14/2021 2:56:20 PM

sitecore\matthew.evanoff

/sitecore/content/JssPublic/Home/JSSMigration/Multi Column/Content/Related Links Right Sidebar

Related Links

10/27/2021 6:39:24 PM

sitecore\spencer.pope

10/27/2021 6:34:42 PM

sitecore\spencer.pope

/sitecore/content/JssPublic/Home/JSSMigration/Related Links/Content/Related Links

Related Links

9/28/2020 1:07:31 PM

sitecore\william.natta

9/2/2020 1:42:37 PM

sitecore\william.natta

/sitecore/content/ProjectSolutions/Home/Conflict Mitigation Processes Toolbox/Content/Related Links

Related Links

4/26/2021 3:37:42 PM

sitecore\william.natta

4/15/2021 3:58:04 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Our Company/About Piedmont/Our Community/Customer Bill of Rights NC/Content/MCNoCache/Related Links

Related Links

4/26/2021 3:37:19 PM

sitecore\william.natta

4/15/2021 3:58:04 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Our Company/About Piedmont/Our Community/Customer Bill of Rights SC/Content/MCNoCache/Related Links

Related Links

4/26/2021 3:37:00 PM

sitecore\william.natta

4/15/2021 3:58:04 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Our Company/About Piedmont/Our Community/Customer Bill of Rights TN/Content/MCNoCache/Related Links

RL News Releases

10/15/2020 11:58:05 PM

sitecore\jerry.wells

3/20/2020 3:09:09 AM

sitecore\courtney.icenhour

/sitecore/content/PNG/Home/Customer Service/COVID 19/Content/RL News Releases

RL Resources

10/15/2020 11:58:05 PM

sitecore\jerry.wells

3/16/2020 12:43:07 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Customer Service/COVID 19/Content/RL Resources

RL State Regulatory Orders

10/15/2020 11:58:05 PM

sitecore\jerry.wells

6/2/2020 1:02:21 PM

sitecore\van.parker

/sitecore/content/PNG/Home/Customer Service/COVID 19/Content/RL State Regulatory Orders

RL News Releases

4/7/2020 1:02:46 PM

sitecore\william.natta

3/20/2020 3:09:09 AM

sitecore\courtney.icenhour

/sitecore/content/PNG/Home/Customer Service/COVID 19/SP/Content/RL News Releases

RL Resources

4/7/2020 1:01:33 PM

sitecore\william.natta

3/16/2020 12:43:07 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Customer Service/COVID 19/SP/Content/RL Resources

RL State Regulatory Orders

4/7/2020 1:01:00 PM

sitecore\william.natta

4/2/2020 5:15:23 PM

sitecore\courtney.icenhour

/sitecore/content/PNG/Home/Customer Service/COVID 19/SP/Content/RL State Regulatory Orders

/Home/Billing/Prepaid-Advantage/Enroll/Content/No-Account

Yes-Account

/Home/Billing/Prepaid-Advantage/Enroll/Content/Yes-Account

MultiStepFormBuilder

/Home/Billing/Prepaid-Advantage/Enroll/No/Content/MultiStepFormBuilder

Yes-Account

/Home/Billing/Prepaid-Advantage/Enroll/Yes/Content/Yes-Account

thirdPartyMultiStep

/Home/Billing/Special-Assistance/Third-Party-Notification/Third-Party-Notify-Enroll/Content/thirdPartyMultiStep

Medical-Certificate-Request-Form

/Home/Billing/Special-Assistance/Medical-Certificate-Request/Content/Medical-Certificate-Request-Form

Move-Common-REMOVED-R5

/Home/Start-Stop-Move/Move/Content/Move-Common-REMOVED-R5

MultiStepFormMove

/Home/Start-Stop-Move/Move/Content/MultiStepFormMove

MultiStepFormMove-COPY

/Home/Start-Stop-Move/Move/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Home/Start-Stop-Move/Move/Content/MultiStepFormMove-Rework

Move-Common-FL-REMOVED-R5

/Home/Start-Stop-Move/Move-DEP-FL/Content/Move-Common-FL-REMOVED-R5

Move-Common-REMOVED-R5

/Home/Start-Stop-Move/Move-DEP-FL/Content/Move-Common-REMOVED-R5

MultiStepFormMove

/Home/Start-Stop-Move/Move-DEP-FL/Content/MultiStepFormMove

MultiStepFormMove-COPY

/Home/Start-Stop-Move/Move-DEP-FL/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Home/Start-Stop-Move/Move-IN/Content/MultiStepFormMove-Rework

MultiStepFormMove

/Home/Start-Stop-Move/Move-MW/Content/MultiStepFormMove

MultiStepFormMove-COPY

/Home/Start-Stop-Move/Move-MW/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Home/Start-Stop-Move/Move-MW/Content/MultiStepFormMove-Rework

MultiStepFormREWORK

/Home/Start-Stop-Move/Start/Content/MultiStepFormREWORK

Start-All-Juris-DEC

/Home/Start-Stop-Move/Start/Content/Start-All-Juris-DEC

Start-All-Juris-FL

/Home/Start-Stop-Move/Start/Content/Start-All-Juris-FL

Stop-Service

/Home/Start-Stop-Move/Stop/Content/Stop-Service

Stop-Service-DEC

/Home/Start-Stop-Move/Stop/Content/Stop-Service-DEC

Stop-Service-MW-not-used

/Home/Start-Stop-Move/Stop/Content/Stop-Service-MW-not-used

Stop-Service-DEP-not-used

/Home/Start-Stop-Move/Stop/Content/Stop-Service-DEP-not-used

Stop-Service-FL-not-used

/Home/Start-Stop-Move/Stop/Content/Stop-Service-FL-not-used

Stop-Service-FL

/Home/Start-Stop-Move/Stop/Content/Stop-Service-FL

Stop-Service-nonDEC

/Home/Start-Stop-Move/Stop/Content/Stop-Service-nonDEC

Stop-Service-COPY

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-COPY

Stop-Service-REWORK

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-REWORK

Stop-Service-REWORK-BU

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-REWORK-BU

Stop-Service-ALL-JUR-old

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-ALL-JUR-old

MultiStepFormBuilder

/Home/Start-Stop-Move/Request-a-Reference-Letter/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Home/Products/Renewable-Advantage/Enroll/Content/MultiStepFormBuilder

Enroll-in-Power-Manager-Form

/Home/Products/Power-Manager/Enroll-Carolinas/Content/Enroll-in-Power-Manager-Form

Enroll-in-Power-Manager-Form

/Home/Products/Power-Manager/Enroll-OH-KY/Content/Enroll-in-Power-Manager-Form

Enroll-in-Power-Manager-Form

/Home/Products/Power-Manager/Enroll-IN/Content/Enroll-in-Power-Manager-Form

Enroll-In-NC-GreenPower

/Home/Products/Renewable-Energy/NC-GreenPower/Enroll-In-NC-GreenPower/Content/Enroll-In-NC-GreenPower

Enroll-Online-Indiana

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Indiana/Content/Enroll-Online-Indiana

Enroll-Online-Indiana-2021

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Indiana/Content/Enroll-Online-Indiana-2021

Enroll-Online-Kentucky

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Kentucky/Content/Enroll-Online-Kentucky

Enroll-Online-Ohio

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Ohio/Content/Enroll-Online-Ohio

MultiStepFormBuilder

/Business/Billing/Automatic-Payment-Plan/Enroll/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Business/Billing/Equal-Payment-Plan/Enroll/Content/MultiStepFormBuilder

SignUpMultiStepForm

/Business/Billing/Time-of-Use-Rate/Enroll/Content/SignUpMultiStepForm

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/DEP-FL-no-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

Stop-Service-No-Fed-REWORK

/Business/Start-Stop-Move/DEP-FL-no-Fed/Stop-Service/Content/Stop-Service-No-Fed-REWORK

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/DEP-FL-with-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

Stop-Service-With-Fed-REWORK

/Business/Start-Stop-Move/DEP-FL-with-Fed/Stop-Service/Content/Stop-Service-With-Fed-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-No-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/MW-No-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-No-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-No-Fed/Move-Service-Indiana/Content/MultiStepFormMove-Rework

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-No-Fed/Start/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-No-Fed/Start/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/MW-No-Fed/Start/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-No-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-No-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-No-Fed-REWORK

/Business/Start-Stop-Move/MW-No-Fed/Stop-Service/Content/Stop-Service-No-Fed-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-With-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/MW-With-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-With-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-With-Fed/Move-Service-IN/Content/MultiStepFormMove-Rework

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-With-Fed/Start/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-With-Fed/Start/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/MW-With-Fed/Start/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-With-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-With-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-With-Fed-REWORK

/Business/Start-Stop-Move/MW-With-Fed/Stop-Service/Content/Stop-Service-With-Fed-REWORK

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/Non-Res-Start-Service-Request/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/Start-DEP-SC/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/Start-DEP-SC/Content/MultiStepFormBuilder-COPY

MultiStepFormREWORK

/Business/Start-Stop-Move/Start-DEP-SC/Content/MultiStepFormREWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/Start-FL/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/Start-FL/Content/MultiStepFormBuilder-COPY

MultiStepFormREWORK

/Business/Start-Stop-Move/Start-FL/Content/MultiStepFormREWORK

MultiStepFormREWORK

/Business/Start-Stop-Move/Start-Service-DEP-NC/Content/MultiStepFormREWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/With-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/With-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/With-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/With-Fed/Start-IN/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/With-Fed/Start-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/With-Fed/Start-Service/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/With-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/With-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/With-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-With-Fed-REWORK

/Business/Start-Stop-Move/With-Fed/Stop-Service/Content/Stop-Service-With-Fed-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/No-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/No-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/No-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/No-Fed/Start-IN/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/No-Fed/Start-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/No-Fed/Start-Service/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/No-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/No-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/No-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-No-Fed-REWORK

/Business/Start-Stop-Move/No-Fed/Stop-Service/Content/Stop-Service-No-Fed-REWORK

MultiStepFormBuilder

/Business/Products/Renewable-Advantage/Enroll/Content/MultiStepFormBuilder

EDA-Multi-step-form-2

/Business/Products/Design-Assistance/Request/Content/EDA-Multi-step-form-2

EDAMultistepForm-COPY

/Business/Products/Design-Assistance/Request/Content/EDAMultistepForm-COPY

EnergyDesignAssistanceMultiStepForm

/Business/Products/Design-Assistance/Request/Content/EnergyDesignAssistanceMultiStepForm

Enroll-MultiStepForm

/Business/Products/Renewables/GoGreen-Indiana-Large-Business/Enroll/Content/Enroll-MultiStepForm

Enroll-in-NC-GreenPower

/Business/Products/Renewables/NC-GreenPower/Enroll-Now/Content/Enroll-in-NC-GreenPower

Enroll-MultiStepForm

/Business/Products/Renewables/GoGreen-Energy/Enroll-KY/Content/Enroll-MultiStepForm

Enroll-in-GoGreen-Ohio

/Business/Products/Renewables/GoGreen-Energy/Enroll-OH/Content/Enroll-in-GoGreen-Ohio

Enrroll-MultiStepForm

/Business/Products/Renewables/GoGreen-Energy/Enroll-IN/Content/Enrroll-MultiStepForm

MultiStepFormBuilderNEW

/Home-Services/Strikestop/enroll/Content/MultiStepFormBuilderNEW

Renew-Strikestop

/Home-Services/Strikestop-Renew/Enroll/Content/Renew-Strikestop

Travel-Request-Form

/Our-Company/Careers/Accommodations/Travel-Form/Content/Travel-Request-Form

MultiStepFormBuilder

/Customer-Service/Email-Us/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Customer-Service/Email-Us-FYB/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Customer-Service/Request-Tree-Trimming/Content/MultiStepFormBuilder

MultiStepFormBuilderNEW

/Spanish/Servicios-al-Hogar/StrikeStop/Inscripcion/Content/MultiStepFormBuilderNEW

MultiStepFormBuilder-Request

/Spanish/Servicio-al-Cliente/TEST-Request-Tree-Trimming/Content/MultiStepFormBuilder-Request

MultiStepFormBuilder

/Test/testmulti/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Automation/Components/MultiStepForm/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Automation/MultiStepForm/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Automation/MultiStepPIForm/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/CI-Test/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/CI-Test/Test-Form/Content/MultiStepFormBuilder

MultiStepFormBuilder-2

/Test/CI-Test/Test-Form/Content/MultiStepFormBuilder-2

MultiStepFormBuilder-3

/Test/CI-Test/Test-Form/Content/MultiStepFormBuilder-3

Enroll-in-Automatic-Payment-Form

/Test/Enroll/Content/Enroll-in-Automatic-Payment-Form

Landlord-Advantage

/Test/Landlord-Advantage-Form/Content/Landlord-Advantage

MultiStepFormBuilder

/Test/Validation/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Validation/584/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Validation/Form-Multi/Content/MultiStepFormBuilder

Multi-Step-Form

/JSSMigration/Multi-Step-Form/Content/Multi-Step-Form

Start-All-Juris-NonDEC

/sitecore/content/SSM/Home/Content/Start-All-Juris-NonDEC

PropertyManagerFormNew

/sitecore/content/SSM/Home/Property-Managers-Requests/Content/PropertyManagerFormNew

Related Links

12/12/2017 8:11:48 AM

nam\jholly1

3/16/2017 3:04:16 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/HealthCheck/Content/Related Links

Related Links

7/15/2016 2:16:34 PM

()
;
i
++
)
{
;
-
1
;
numberOfCols
};
numberOfCols
};
};
};

TechnicalOverview

hashtag
Component Creation - Technical Overview


Table of Contents:

  1. Intro

  2. Technical Overview

  3. Practical Overview

  4. Definition of Done

  5. Sitecore


hashtag
A Technical Overview


hashtag
Generate Component Factory

The Generate Component Factory (scripts/generate-component-factory.js) generates the /src/temp/componentFactory.js file which maps React components to JSS components.

The component factory is a mapping between a string name and a React component instance. When the Sitecore Layout service returns a layout definition, it returns named components. This mapping is used to construct the component hierarchy for the layout.

The default convention uses the parent folder name as the component name, but it is customizable in generateComponentFactory().

Sometimes Sitecore references a component in a way that is different from what we want the component to be called within the application. In this case, we can provide an alias to map the Sitecore name to our component upon import:

In this case, we are telling our app that if it receives a string called HeroCarousel or HeroCarouselNocache, use the 'Hero' component. Likewise, if a string of JssAccordion is encountered, use the 'Accordion' component, and so on.

If the name of your component and its Sitecore counterpart are the same, there is no need to include an alias.

hashtag
Passing Down Data Via Composition

At its simplest, the goal of the composition file is to transform the data being passed to your component from accessing deeply nested properties like this:

into this:

What does that entail exactly?

The composition file also exists in the your component directory. Sitecore sends a wealth of information down to the page related to each component. Not all of this information is needed or even useful. So the purpose of each of these functions is to strip away the unneeded data, giving our component only what it needs to render properly.

To illustrate this, in the following example, the QuickLinks component will get a single items prop returned to it, which will consist of an array of objects:

As a developer, you will be responsible for writing the composition function for your component. Since the data from Sitecore comes in all sizes and shapes, there is no "one-size-fits-all" solution, but most likely you can take a cue from the composition functions that have been written before to get a feel for how to approach a new one.

hashtag
Adding files for your component

Now that you know how components are implemented within our app, how is a component actually created within the app?

Generally, each component you create will consist of at least one of the following items:

  • an - your React file.

  • a file for transforming incoming data into more concise props

  • a - a file for your unit tests.

Read on for more detail.

hashtag
Index.tsx

The index file will be your main component React file, using Typescript. At a minimum, your component will require this file. Since our project uses Tailwind, your styles will mainly exist here as well. for more info.

hashtag
Test.tsx

The test file is where your unit tests live. Tests are written in React with Typescript, using [Jest](https: //jestjs.io/en/) and [Enzyme](https: //enzymejs.github.io/enzyme/).

Jest is a popular testing library and Enzyme adds some sugar on top to make writing the tests a little easier.

You should be testing the basic functionality of your component. This may be as simple as validating that your component is rendering to mocking data and checking to make sure your component is outputting it correctly.

In all likelihood, you will need a test for every component you write, even if it is a very small test to confirm that the component is properly mounted.

hashtag
Types.ts

This file is for type definitions and interfaces for [Typescript](https: //www.typescriptlang.org). You can store your component-specific definitions in this file and export them to your index file.

For more information on writing Typescript within our app, please check out our Typescript Style Guide.

hashtag
Stories.js

The stories.js file creates a component. To be honest, the paradigm of writing in Storybook can be a little bit tricky to grasp at first if you don't have experience with it. And to be frank, their docs could use some work. So you are encouraged to .

In essence, however, consider your Storybook file something like an environment for your React component that enables you to run it in isolation from the rest of the app. This makes it easy to preview the functionality and, as a result, developers and designers can easily access the React component without having to navigate to a route to interact with it. This also enables you to view and manipulate it in ways that would be difficult in the context of an entire app.

If your component is extremely simple and self-contained, a Storybook file may not be necessary, or perhaps even possible. But in general, all components should have a Storybook file.

Unlike most of our other component files, we are not using Typescript in Storybook, so it is not necessary to provide types within your story file.

hashtag
Data.js

The data.js file is meant to provide mock data to your unit tests and Storybook files. It is ignored by Webpack, so don't include any objects or functions your component needs in order to render properly.

The data file does not use Typescript since it is generally only used to provide mock JSON data.

Perhaps the most frequent use case for this file is that you can copy/paste the data exactly as it comes from Sitecore. Since Storybook files and unit tests exist in isolation, they won't receive Sitecore data. But your React component will be relying on data to render correctly. So the dummy data placed within the data file ensures your React component will still have similar data in the same shape as the Sitecore data it would have in real-world conditions. Thus, even in isolation, you can ensure your component looks and behaves in a consistent and predictable manner.

On occasion, you may encounter a situation where you have a hard-coded object or map that is used for reference within your app. While these can be useful, sometimes they may clutter up your main index.tsx file. In this case, you may be tempted to house those values in the data.js file and import them into your React component from there. But as was mentioned above, these files are ignored by Webpack, so any necessary additional data you would want to exist in a file should be added to a file named something other than data.js.

Depending on the complexity of your component, this file may not be necessary. For example, if your React component doesn't get called from Sitecore, doesn't have JSS fields, or it doesn't require any extra hard-coded data, then you will not need a data file.

hashtag
The folder

Of course, all of these files must live somewhere, and that place is in a folder. This folder should of course be named after your component and added to the /components directory. Resist the temptaion to take creative liberties with the name of the folder, as this will be the folder that will be imported into the Component Factory (see /src/temp/componentFactory.js) and importing a folder named TheGreatestComponentYouHaveEverFeastedYourMiserableEyeballsOn for the Modal component won't make a lot of sense out of context, no matter how fitting it seemed in the moment.

< Previous Next >

Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

Installing Citrix Workspace on Chrome OS Devices

Installing, Configuring, and Uninstalling Citrix Workspace on Chrome OS Devices

Contents

****

Installing Citrix Workspace on Chrome OS Devices ............................................................. 2

Configuring Citrix Workspace on Chrome OS Devices.......................................................... 3

Secondary configuration of Citrix Workspace on Chrome OS Devices…………………. 6

Uninstalling Citrix Workspace on Chrome OS Devices ......................................................... 9

\

Installing Citrix Workspace on Chrome OS Devices

1. On the Chrome OS device, open the Chrome Web Store and search for ‘Citrix Workspace’. Select ‘Citrix Workspace for Chrome OS’. Click Add to Chrome and Add App When prompted.

****

Configuring Citrix Workspace on Chrome OS Devices

****

1. Upon installation, click the Launcher Icon and then click the Citrix Workspace icon. You may need to click on All Apps to see the Citrix Workspace icon.

****

2. You will be prompted to enter a server address: Enter

****

3. Next, enter your Username, Password and your Passcode when prompted. Click Log On once you’ve finished entering your information.

NOTE:

• When using a physical key fob, your Passcode is your unique PIN + RSA token ID.

• When using a soft token, the PIN is not included as part of the Passcode.

4. The available Citrix applications and desktops should now appear as icons. You have successfully installed the Citrix Workspace software on your computer.

5. To launch a virtual desktop, select the Desktops tab at the bottom of the screen and select the assigned desktop. And to launch a virtual application, select the Apps tab at the bottom of the screen and select the application.

****

****

****

****

****

****

****

****

****

****

****

****

Secondary configuration of Citrix Workspace on Chrome OS Devices

****

****

NOTE: If you have not set up an authentication method in O365, by using this job aid , please do so BEFORE adding the Multifactor Authentication Citrix Portal URL

****

    • Be sure you set up a primary authentication method and a secondary authentication method.

****

o Choose your preferred primary authentication method from the drop-down box under “what’s your preferred option?”

****

1. Upon installation, click the Launcher Icon and then click the Citrix Workspace icon. You may need to click on All Apps to see the Citrix Workspace icon.

2. You will be prompted to enter a server address: Enter in order to setup the Citrix Portal that utilizes Multifactor Authentication. Afterwards, select Connect.

3. Next, enter your Username and Password when prompted. Click Log On once you’ve finished entering your information.

****

NOTE: You will be prompted to authenticate via the method that you setup within your Office 365 Authentication Profile:

· Via a call to a phone number you provided during setup (e.g., mobile phone number, secondary mobile phone number, or home phone number where you will be working)

· The Microsoft Authenticator application

****

****

4. To access your assigned virtual desktops or virtual applications, click either the DESKTOPS or APPS buttons at the top of the page.

****

Uninstalling Citrix Workspace on Chrome OS Devices

1. Open the Launcher (Magnifying glass) on your taskbar. Click the upward arrow to show all installed apps and locate the Citrix Workspace icon.

2. Right-click the Citrix Workspace Icon. If you are not using a mouse, then press the Alt key when you click on the Citrix Workspace icon to bring up the ‘right click’ menu options

3. Click Uninstall. Click Remove when asked to confirm removal.

****

Code Review:

hashtag
Code Reviewsarrow-up-right

  • Created by Anonymous, last modified on Oct 08, 2020arrow-up-right

"Everybody is junior at something", or so they say. You may be fairly early on in your career but have some experience working in a large enterprise or business where structured Code Reviews were the order of the day. Or you may have many years experience on a loose team that didn't really take Code Review very seriously. Thus, you may find that while you are a senior developer, you are "junior" (I honestly hate that term) when it comes to Code Reviews, and vice versa. Whatever the case, Ninja Turtles values Code Review. Let's talk about it.

The purpose of this document is to create a discussion from which we may create a kind of Working Agreement around Code Review on Ninja Turtles. It sets out to do so by addressing the following:

  • The current state of our code review process

  • Suggestions about moving forward

  • How to improve at Code Reviews and PRs in general

hashtag
State of the Code Review

The way that Ninja Turtles does Code Review is pretty much exclusively via Pull Requests; if you are a developer on Ninja Turtles, this is where most of your feedback will come from. At present, the most experienced of our team does most of the Code Review, and indeed it sometimes seems as though every PR is reviewed by 1 or 2 people, one of whom approves the PR before it is merged. Put simply, it's sort of the exemplification of the in action. For the purposes of this conversation, I will refer to the ones doing most of the reviewing as "the 20%" (following the adage that 80% of the work is done by 20% of the team) and the devs that are doing less of the reviewing as "the 80%". Obviously this is not exact, but you get the idea.

The goal should be to get all developers to the point where they're contributing (and receiving) a more equitable share of Code Review, rather than the current 80/20 divide.

hashtag
Why Improve Code Review?

As noted above, Ninja Turtles is a team that benefits from a diversity of experience levels, but the natural consequence of this is that the developers with the most knowledge and experience with code review (the 20%) end up doing most of the code review. This stands to reason, and is actually quite normal, and probably beneficial. But it can be taxing for those devs doing the lion's share of the reviewing. It is a knife that cuts both ways; while those experienced devs may begin to feel resentment that the responsibility rests on them, the other devs feel inadequate, that they're not pulling their weight, or that they aren't given a fair chance to review before the stuff in their wheelhouse is "taken". Thus, everyone gets further entrenched in their respective roles. This is not great, but it's a problem that won't solve itself. It will require action as a team (more on this later).

But beyond that, there are several compelling reasons for us to improve on our Code Reviews.

  1. From a development standpoint, Code Review is the least expensive way to catch mistakes.

  2. Code Review provides opportunities to spread domain knowledge across the team, thereby preventing a complete reliance on trickle-down economics, er, knowledge.

  3. Code Reviews can provide opportunities to learn, mentor, and teach.

"Effectiveness of code review for determining faults in software is between 30% and 35% more effective than standard unit testing"

  • Steve McConnell, Code Complete

So, we understand the value of Code Review and we see a need to improve, but exactly how do we improve them?

hashtag
Going Forward

I did a lot of reading and consulted some mentors to arrive at some of these conclusions. I will provide the resources I consulted below; I found them to be quite useful, and I encourage all who are interested to review them. After this, I propose a few changes. I will lay them out here and my hope is we can have a conversation about which changes we would like to adopt going forward and thus create something of a "Working Agreement" surrounding Code Reviews. Doing so obviously won't cause a miraculous change overnight, but I think implementing some of these suggestions is a good first step toward moving away from the 80/20 divide described above and getting everyone on the same level.

hashtag
General suggestions:

  • With two approvals now required to on a PR, perhaps it would make sense to rotate a more senior reviewer with a less senior reviewer or two (this would fall inline with guidelines I've seen) and let them tackle it independently. Ideally, they are both able to approve or provide feedback to the dev until they are ready to approve, but if not, after a certain period of time, the devs could come together and talk about why they are not feeling comfortable approving the PR.

  • Maybe we should define our a little bit more clearly (currently, it has exactly one entry ) and we should definitely set up linting to enforce some of the simple things, such as consistent formatting. Devs should not be pushing up code that has completely preventable errors and reviewers should not be wasting precious cognitive energy on simple errors that can be caught by computers

hashtag
Things to think about and maybe discuss:

  • Expecting someone to get better at code reviews by simply looking at someone else's code just isn't a very good or efficient methodology. (See Jim Bird's article below). What can we do bridge that gap in a more proactive way? Perhaps some kind of structured "pair reviewing"?

  • I touched on this above but another thing that Jim Bird suggests is keeping the number of reviewers small. In the long run, I see the value in having all the devs swarm it and knocking out a PR, but in the shorter term, it might make sense to slow down and focus on the quality over quantity and getting all devs up to around the same contribution level?

hashtag
For the 20% currently doing 80% of the reviewing:

  • Consider alternating PRs. Maybe everyone doesn't need to look at every PR, but a few people look at different ones?

    • Not sure what this would look like, but I'm sure it's doable.

  • Step back. Don't be the first to review. Give some space to let some of the other devs (the 80%) step up. This will hopefully take a bit of the pressure off of you and lets the 80% leave the kind of feedback that may be easier for them to recognize as they level up their Code Review game.

hashtag
For the 80%:

  • for things you want to review. Below you'll find an overview of the kinds of things to look for.

  • Step up.

  • If we implement the changes suggested above, you will have some room to make some suggestions early on. But having the 20% step back only works if the 80% step up.


Sources:

Duke Company

hashtag
Portal Guide - Home

Excerpt


The Modern Portal Project Is Complete! »

The Modern Portal Project Is Complete! »

Sign Up for Site Manager Training »

LAN IDs, Employee IDs, and Responsibility Codes do not currently show in modern search on a person's profile card. Please use our specialized People Search Page or access the information in Delve.

The Portal is your default home page when you launch the Internet on your Duke Energy workstation. You can also access the Portal from your personal laptop or mobile device.

The Portal is a robust communication tool designed to be informative and engaging. On the home page, top company stories and employee features are posted frequently. The home page also contains media articles, company events, and personnel announcements. You are encouraged to add comments to articles and submit company-sponsored events and accomplishments.

The Portal also provides access to key transactional items such as company facts and branding, HR information (e.g., benefits and pay), policies, services (e.g., mail service and expense management), departments, operations, and other tools and applications.

hashtag
Portal Site Managers

Brand, Creative & Digital

Contact & Personal Information

Contingent Worker Actions

COVID-19 Employee Resources

COVID-19 Manager Resources

Customer Experience & Services

hashtag
PORTAL GUIDE

hashtag
Manage Your Information

Delve profile pages exist for everyone with a LAN ID who is an employee of or contingent worker for Duke Energy.

Where did my profile information come from?

The organizational and location information in your Delve Portal profile comes from Active Directory.

This section of your Delve Profile is optional. You can choose to enter additional information about yourself, including:

  • About me

  • Home phone

  • Projects

Filling in this information will help others find you when they're looking for someone with knowledge or experience about your areas of expertise, or it can help you connect with others who share your interests.

To edit this information, go to your and click the Update Profile button.

Manage Your Photo in Workday

Manage Your Photo in O365

Instructions for O365 Photos

When you go to Delve, click Me in the left navigation. Use the camera icon over your photo to change your picture. This will change it throughout the O365 environment, including Outlook, Teams, and SharePoint. Changing your photo here will not push it to Workday. Work is underway to enable the Workforce Photo Tool to integrate with O365.

Photo Guidelines

Your photo must be business-appropriate. That would be:

  • A forward-facing, head-and-shoulders shot of you without sunglasses, ball cap, kids or family dog serves as a good starting point. Consider the limited space that will display your face when you select a photo. The goal is to see what you look like!

  • Please follow copyright rules when selecting a photo to update. You must own rights to any image you post.

hashtag
PORTAL GUIDE

hashtag
Accessing the Portal Remotely

You can access the Portal from any computer or mobile device. You do not need to be on the Duke Energy network, and you do not need to use a Duke Energy computer or device that is in the Personal Mobile Device program.

From the SharePoint App

  • You can download the SharePoint app from an app store. The home icon in the app takes you to the Portal homepage.

From a Mobile Browser

  • Open your web browser (i.e., Edge, Chrome or a similar Web browser)

  • Type in the address bar at the top of your Web browser and press Enter.

  • If you are not on the network or on a device managed through the Personal Mobile Device program, you will need to log into your Office 365 account.

Keep in mind that some Duke Energy applications, are not available when you are off the network, and you will experience errors if you click links to them.

hashtag
PORTAL GUIDE

hashtag
Intranet Design Awards

The top ten intranets in the world, published annually by the Nielsen Norman Group. We purchase and publish these reports so that we can get a glimpse behind firewalls into the best practices with other companies. Duke Energy is a two-time winner.

hashtag
PORTAL GUIDE

Personal

chevron-rightTwo External Monitors (Macbook Air)hashtag

Hi, I have a macbook air... is it at all possible for me to find a workaround that allows f

Incident ID: INC000032293420 Last Modified Date: 11 Mar 2022 18:59:06 UTC Assignee:

https://dukeenergy-vchat.onbmc.com/eschat/chat.jsp#/chatarrow-up-right

Hi ,

Welcome to Support Chat. Please enter a question and a chat technician will soon be with you. Just type in a question, keyword or phrase below and I’ll take you to the information you’re looking for.

Bryan1:59 PM

Hi, I have a macbook air... is it at all possible for me to find a workaround that allows for me to run two external monitors in addition to the builtin one?

System1:59 PM

The following associated data has been added:

  • Customer Information

  • Incident: INC000032293420

System1:59 PM

The following associated data has been modified:

  • Customer Information

  • Incident: INC000032293420

System1:59 PM

System Message: Jamal Clair is online and ready to chat.

Jamal Clair1:59 PM

Hello my name is Jamal. Can you please provide your employee id, contact number, Computer serial number, and current location please?

System2:02 PM

System Message: undefined has rejoined the session

2:03 PM

Contact number: 551-254-5505... Serial Number is: FVFGX02PQ6L8 and my current location is NY,NY. I am not sure what my employee number is or how to access it but I am going to try to look into it.

2:04 PM

Could it be this: CW-Professional 47146?

Jamal Clair2:04 PM

Thank you

Jamal Clair2:04 PM

Do you have a dockingstation

Jamal Clair2:04 PM

docking station

2:08 PM

Yes two actually, but unfortunately the macbook air was the m1 chip was the one model apple made that doesn'‌t nativley support multiple screens. This is one of my first weeks, I think my manager said it was an accident and that I was supposed to be sent a macbook pro but I have no idea if that is still in the works. Various sources on the internet claim to have found workarounds but so far I have not been able to successfully replicate these '‌hacks'‌. If the firewall allowed me to cast via airplay then I think I could connect to my external displays wirelessly. As I speak this computer is driving three large external screens but all three are a mirror of each other with the only distinction being the builtin display which is not mirroring the other three screens.

Jamal Clair2:09 PM

is this your personal device

2:09 PM

no it is my duke device

2:10 PM

My personal computer is a pc which has no issue with this

Jamal Clair2:10 PM

so you want to be able to control multiple screens with airplay

2:10 PM

As a last resort

Jamal Clair2:10 PM

but duke firewall blocks that

2:11 PM

ideally I would be able to drive two or three external screens in the (extend... (as opposed to mirror))-display mode

2:13 PM

I don'‌t have much expertice but I figured someone else may have had this issue and found some other solution... like for instance an external gpu that can drive the extra screens ? idk tbh but prior to this job I got very comfortable with a three screen workflow and it isn'‌t the end of the world but going back down to two or one screens is a pretty significant inconvenience considering one is usually dedicated to a microsoft teams call / screen share

2:15 PM

I feel like I may be blowing this out of proportion... this is just a matter of preference that I have grown accustomed to and is not actually a necessity. I have a creeping fear that it may just not be possible with this computer.

\

hashtag
BitLocker keys for LAPTOP-9LGJ3JGS

  • OPERATING SYSTEM DRIVEKey ID:\ f035f7c1-b011-4a97-ad86-a1e2b994037d\ Recovery key:\ 277827-520718-692857-678557-601997-238381-127116-121748

Setup

This section outlines getting the application (for the JSS public site) setup for development.

  1. Get the repo

    1. cd to where you keep your repos

DefinitionOfDone

hashtag
Component Creation - Definition of Done


Table of Contents:

  1. Intro

👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Intro

hashtag
Component Creation - Intro


Table of Contents:

  1. Intro

Sitecore

hashtag
Component Creation - Sitecore


Table of Contents:

  1. Intro

🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverview

hashtag
Component Creation - Practical Overview


Table of Contents:

  1. Intro

Analytics

Adding analytics to a component is pretty straightforward,

First set up your analytics object in your composition method:

Then import the track object from src/lib/Analytics into your component, and grab the analytics data from the composition props. After that, simply attach a handler to the correct component node, to call the track.component method and send the data to analytics:

Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • hashtag
    Our "Definition of Done" for a Component

    You will know your component is complete if it is properly formatted, you have the requisite files outlined in the previous section (where applicable), and your files are free of TypeScript and ESLint warnings. Additionally, the appearance of the component should match the look of the Abstract design if one is provided, and if any Acceptance Criteria is provided, that should be met as well. We prioritize accessibility as well, so your component should pass [WCAG2.1 a11y requirements](https: //www.w3.org/WAI/standards-guidelines/wcag/glance/). This can be validated via the Storybook plugin we have set up, as well as in the browser using Lighthouse.

    Lastly, you will need to have written some unit tests to check the core functionality of your component and ensure that those are passing.

    hashtag
    Checklist


    In summary, your component should do the following...


    If you have met all of these requirements, then you, intrepid developer, have a component worthy of submission to PR!

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • hashtag
    Introduction

    To understand how a component is displayed within our application, it helps to understand the process by which it is rendered within our React app.

    This is a somewhat complex process, but the simplified overview is this:

    • When a user navigates to a route, the React app receives a big bundle of data from Sitecore, including a list of components, and the relevant data for those components.

    • That list represents the building blocks of the page that the user has navigated to.

    • Those "building blocks" are checked against the component list of our application, and when a match is found, a React component is returned, along with the data that Sitecore sent along with the component that it needs to render properly.

    • If a match is not found, a "placeholder component" is rendered instead. Within the React app, this is exactly what it sounds like: a big, ugly "placeholder" to remind you that this component has not yet been created for the app:

    Oops! 🙀 we dont have Global Alerts yet!\

    Let's dive in and get a little more insight.

    Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • hashtag
    Sitecore and You

    In the context of our application, we treat Sitecore like a CMS for our React front end. We use it to tell our application where components should be and provide data and assets.

    But Sitecore is not for the faint of heart. Many devs like to get their hands dirty by just "getting in there" and poking around. However, due to its terrible UI and grinding sluggishness, working within Sitecore can be maddening. To top it all off, as is so often the case, the documentation for Sitecore is substandard. God willing, you won't need to venture into Sitecore very often. But if you have exhausted all other options and you still find you need to work within it, it's better to go in with a clear gameplan of what you want to accomplish and how to accomplish it. This section aims to help you with that.

    hashtag
    Sitecore Component Creation Overview

    Once upon a time, new components were built by the front end team and then handed off to the Sitecore developers for integration into the application. These days, components built by the front end team stay within the front end application, making the workflow much more efficient. New components can then be integrated into the CMS for use by the content team, with editable fields intact.

    While many components are already available for our application in Sitecore, if you find that you need to add a component to be edited within Sitecore, this document contains an overview to the process you will need to follow.

    Before delving into that, it is helpful to understand at a high level how Sitecore is being used in our implementation.

    A "page" such as /Home or /Billing consists of a collection of Renderings within Sitecore. A rendering could be viewed as a representation of a React component within Sitecore. These renderings are associated with data relevant to the component and placeholder info. A Placeholder is a way for content authors and marketing folks to specify where upon a page a component should be injected. In order for a rendering to be displayed upon a page, it will need to be assigned a placeholder.

    When a page exists within Sitecore, it is made available to our application via something called the Layout Service, which exposes an endpoint that can be requested. This endpoint returns a serialized JSON payload of the rendering info from Sitecore. While a traditional Sitecore implementation would involve C# and Razor templates, this implementation makes it possible for us to use React on the front end.

    hashtag
    Basic Instructions for Creating a Component

    1. Navigate to [Sitecore](http: //scdev25.duke-energy.com/sitecore) and login. From there, expand "sitecore > Layout > Renderings > Custom > Common" and you can see the available renderings. If you need a rendering to map to your component that isn't already available here, you can create one.

    2. Create a Rendering. Within the appropriate folder under /sitecore/layout/renderings, create a new Json Rendering. This will most likely be within the Custom/Common folder, and can be done either by right-clicking the folder and selecting Insert > Json Rendering or selecting the folder you want to create a rendering in and then pressing the Json Rendering button from the options.

    3. You will be prompted by a modal to choose a name for your item. In the input labelled "Enter a name for the new item", provide the name of the React component that will be used for the rendering.

    4. You can now add your newly-created Json Rendering to a page. If appropriate, you will also need to specify a datasource item for the rendering. The datasource item will need to be created from a data template in Sitecore.

    5. When Layout Service is rendering, the fields from the datasource item will be serialized and added to the Layout Service output and should now be available to our application.

    < Previous

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Code Review builds a stronger foundation of trust or something

    Consider limiting how much code you will look at, at least in one sitting; looking at code you didn't write for a long time can be exhausting. You might consider timeboxing your reviews to one hour and come back later if there's more you feel that needs to be done, or you may set a line count of around 200-400 lines before taking a break and doing something else for awhile.
    • This may mean that we add a Bitbucket integration to Slack or Teams (if that's available) or when a new PR is submitted, the submitter alerts the team

  • Be specific when answering questions. Answering a question about how or why something works with a block of code will solve the problem, but it doesn't help the asker understand the issue any better. A link to an article or MDN can be sufficient.

  • Ask questions. Even in PRs. Questions like "Why are we doing it like this?" or "I'm having trouble understanding this block of code, can you explain it to me?" are totally valid within a PR. Doing it in the PR creates a paper trail of the conversation. If you've reviewed the code and you don't have feedback but you also don't feel you can approve the PR, it might be an indication that you need to ask more questions.

  • Ask to pair with somebody more experienced at reviewing code. You can get a feel for their process and ask questions about how they approach Code Reviews and spot things.

  • Don't underestimate the value of the input of someone with less context. Having anyone that isn't you review your code is helpful, because we all get myopic about our own code and can miss things that are really obvious to a fresh perspective.

  • 80/20 rulearrow-up-right
    style guidearrow-up-right
    Have an algorithm/checklistarrow-up-right
    https://www.javacodegeeks.com/2014/08/dont-waste-time-on-code-reviews.htmlarrow-up-right
    https://www.youtube.com/watch?v=EXoSuqIp6Ns&feature=youtu.bearrow-up-right
    http://amlyhamm.com/talks/code-reviews/#/arrow-up-right
    https://www.braintreepayments.com/blog/effective-pull-requests-a-guide/arrow-up-right

    Typescript Rules:

    Warnings

    clone the project from https: //bitbucketp.duke-energy.com/projects/DUKCOM/repos/dxt-jss-public/browse 1. In your terminal (command line), run git clone https: //bitbucketp.duke-energy.com/scm/dukcom/dxt-jss-public.git
  • Install dependencies

    1. Use node -v 14.17.4

    2. Set npm registry to Nexus (prod)* 1. run npm config set registry https: //nexus.duke-energy.com/repository/duke-cne-npm/ on the command line

    3. Run npm install on the command line

  • Setup .jssconfig and .env

    1. Add scjssconfig.json file (or rename scjssconfig-sample.json → scjssconfig.json)

      //scjsstest.duke-energy.com" } }

  • Add .env file (or rename .env-sample → .env)

    1. Add:

      //` for development so that jurisdiction cookies will work properly.

  • Setup your .hosts file

    1. We need to set the local IP to local.duke-energy.com so we can use cookies set by the .duke-energy.com domain.

    2. On the command line, run sudo vim /etc/hosts

    3. Add a new line and enter 127.0.0.1 local.duke-energy.com

    4. Press Esc key and then :x to save and exit

  • Start the project for development.

    1. Inside the project, run npm run start:connected on the command line.

    2. After the app successfully starts up, change the URL to https: //local.duke-energy.com:3000/home.

  • Do some cool stuff

  • *Why are we using Nexus rather than npm for package installs?

    @de-electron is a scoped npm package for [Electron Design System](https: //electron.duke-energy.com) dependencies which lives in Nexus, Duke Energy's private internal package repository. You can use Nexus to install all necessary npm packages for the project.

    a types.ts filearrow-up-right - for Typescript definitions and interfaces

  • a stories.js filearrow-up-right - for Storybook..

  • a data.js filearrow-up-right - for mock Sitecore data.

  • a 📁 containing each of these filesarrow-up-right - It's a folder.

  • index.tsx filearrow-up-right
    composition.tsxarrow-up-right
    test.tsx filearrow-up-right
    See the styles sectionarrow-up-right
    Storybookarrow-up-right
    check out our documentation for Storybook.arrow-up-right
    hashtag
    Testing Analytics

    When not in production, analytics data will be sent to the console instead of to Google. To verify that your analytics are being setup and used correctly, simply open the console, trigger the analytics method, and you should see something similar to the following:

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    const { compositionFunction, component } = Composition(NavCard)(({ fields, params }) => {
    
    const items = fields?.items.reduce(
        (acc: Parameters<typeof NavCard>[0]['items'], curr: typeof fields) => {
          return [
            {
              ...
              analytics: {
                category: 'nav-card_rectangular',
                label: title?.value || '',
                action: [cta?.text?.value, cta?.href].filter(Boolean).join(' | '),
                guid: link?.value?.id || '',
                event: 'event-click',
              },
            },
          ];
        },
        []
      );

    Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • If you'd prefer to get right to the point and skip the overview, there's a handy checklist herearrow-up-right. Otherwise, read on for an in-depth explanation of how one could approach a workflow via Storybook.

    hashtag
    Workflow - A Practical Overview

    As a developer, you may find it convenient to develop your React component in isolation within Storybook, rather than developing on the page. This can give you the opportunity to preview it and get a feel for the data you will need. Once the component looks and behaves in the intended manner, it should be relatively simple to incorporate it into the broader application.

    Where before we have focused on a logical overview of how components work and are used within the context of our Sitecore JSS setup and our custom React application, this section will focus on a practical suggested workflow for developing your component.

    hashtag
    The Approach

    Whether or not you elect to decide to develop your React component with Storybook, you will need two things to get started: an index.tsx and the data to render with it (if your component gets data from Sitecore).

    It will be challenging to know in advance the shape of the data that comes to your React component from Sitecore, so you may find it helpful to set up your component in a very basic way that simply accepts props and renders or logs them out. Then, you can set up a composition function that does the same.

    Once your component is rendering, you can capture the data from Sitecore that is logged or displayed onscreen to create your data file. Then you can begin working on your composition function to ensure you are sending only the data your component needs in the simplest, flattest form possible.

    As you consider the data your component will need, it might be a good time to think about how your component is supposed to behave and consider writing out some assertions to test against in your test.ts file.

    After that, you can work in Storybook by importing your component's composition function into your story.js file and process your dummy data with it. At this point, you will have dummy data that looks just like what is coming from Sitecore and you can develop your component in Storybook, just as you would a regular website.

    An example of that might look something like this:

    Finally, once your component is close to completion, you can finish writing your unit tests to ensure your component functions as expected.

    The following section summarizes things into a concise checklist.

    hashtag
    Checklist

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

      1. Add:
    
         ```
         {
           "sitecore": {
             "instancePath": "",
             "apiKey": "{9F777224-3275-4D56-BD29-371FB3C00821}",
             "deploySecret": "",
             "deployUrl": "",
             "layoutServiceHost": "https:
      ```
      HTTPS=true
      ```
    
      This will allow you to use `https:
    const componentAlias = {
      HeroCarousel: "Hero",
      HeroCarouselNocache: "Hero",
      JssAccordion: "Accordion",
    
      // ...
    };
    <Component title={fields.PodItemLink?.value?.text} />
    <Component title={title} />
    // composition/index.js
    
    const QuickLinks = ({ fields }) => {
      const items = fields?.QuickLinkItems?.reduce(
        (acc, curr) => [
          ...acc,
          {
            image: { ...curr.Icon?.value },
            link: curr.Link?.value?.href,
            title: curr.Title?.value,
            id: curr.Id,
          },
        ],
        []
      );
      return { items };
    };
    // components/QuickLinks/index.tsx
    
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    import track from 'src/lib/Analytics';
    ...
    
    const ComponentName = ({
      ...
      analytics,
    }: PropsType) => {
    
    ...
      <Button
        ...
        onClick={() => track.component(analytics)}
      >
        Click Me!
      </Button>
    // MyComponent/composition.tsx
    
    const MyComponent = (sitecoreData) => {
      return sitecoreData;
    };
    // MyComponent/index.tsx
    
    /** technically, you would want to provide types in a Typescript file,
     * but we're just trying to get some data here.
     */
    
    const MyComponent = (scData) => {
      console.log(scData);
      return <div>{JSON.stringify(scData)}</div>;
    };
    
    // Now you can copy the payload from your browser and paste it into your brand new data file!
    // story.js
    import React from "react";
    import MyComponent from "./index";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    import Data from "./data";
    
    /**
     * create a variable with data that is in the shape that your component will be
     * expecting, as it would be from your composition file.
     * Again, this is necessary since the composition function is not called by Storybook.
     */
    
    const props = MyComponentComposition(Data);
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      /* here you can spread those props into your Storybook file, making data
       * available to your component as it would be on the actual live page.
       */
      ...props,
    };

    Your office phone may automatically pre-fill; HOWEVER, be sure to uncheck this option. Your primary and secondary methods should be a combination of: a mobile phone number, secondary mobile phone number, a home phone number where you will be working or the Authenticator applicationarrow-up-right

  • The Authenticator application is the preferred primary method of authentication.

  • https://www.myapps-duke-energy.comarrow-up-right
    How to Add or Change your O365 Profile Authentication methodarrow-up-right
    https://www2.myapps-duke-energy.com/Citrix/Duke-Energy-MFAWeb/arrow-up-right
    Skills and expertise
  • Schools and education

  • Interests and hobbies

  • Enter your LAN ID and password.

  • Follow the steps required for your multi-factored authentication (MFA). This is most likely a phone call to the number you entered when you created your Office 365 profile. For questions, call the Help Deskarrow-up-right.

  • Delve profilearrow-up-right
    https://dukeenergy.sharepoint.com/sites/portalarrow-up-right
    Site Manager Guide»
    Sign Up for Site Manager Training »

    Typescript

    In typescript global types can be declared in a .d.ts file and used anywhere without explicitly importing them. Our project's .d.ts file is named project.d.ts .

    It contains:

    1. Some library types in the form of [triple slash directives](https: //www.typescriptlang.org/docs/handbook/triple-slash-directives.html). These need to be placed at the top of the file.

    2. Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).

    3. Our own global types.

    Typescript provides many [Utility Types](https: //www.typescriptlang.org/docs/handbook/utility-types.html) which are useful for manipulating the base types in the global ComponentTypes interface.

    A few basic ones to know:

    hashtag
    Pick<Type, Keys>

    Only use the specified Keys from the Type.

    hashtag
    Partial<Type>

    Allows the type to be optional (undefined)

    hashtag
    Required<Type>

    Opposite of Partial, the type must be defined

    Using the stategies above you can select types from the global source and compose them to create a representation of the props in a specific component. While the global types live in project.d.ts , component level types should generally be placed in a types.ts file within the component directory and imported for use.

    Although ComponentTypes is a ✅ _Good starting place, some components may require a type that is more specific and not usefully included in the global declaration._


    hashtag
    Naming

    • {['class', 'enum', 'interface', 'namespace', 'type', 'variable-and-function'].map(item => (

    • {item.split('-').join(' ')}

    • ))}


    hashtag
    class

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers/methods use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    enum

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    interface

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    namespace

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    type

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ ✅ Good


    hashtag
    variable and function

    🐪 camelCase

    🚫 Bad

    ✅ Good


    React | Typescript | Tailwind | Forms | Unit Tests

    Typescript Interfaces

    When we talk about a type in TypeScript, we mean a collection of things that you can do with a variable (or expression). You might be able to read or write a given property, call a function, use the expression as a constructor, or index into the object. Some objects (like Date) in JavaScript can do nearly all of those! In TypeScript, interfaces are the most flexible way of describing types.

    You'll see interfaces used to describe existing JavaScript APIs, create shorthand names for commonly-used types, constrain class implementations, describe array types, and more. While they don't generate any code (and thus have no runtime cost!), they are often the key point of contact between any two pieces of TypeScript code, especially when working with existing JavaScript code or built-in JavaScript objects.

    The only job of an interface in TypeScript is to describe a type. While class and function deal with implementation, interface helps us keep our programs error-free by providing information about the shape of the data we work with. Because the type information is erased from a TypeScript program during compilation, we can freely add type data using interfaces without worrying about the runtime overhead.

    While that sounds like a simple, one-purpose task, interfaces role in describing types becomes manifest in a large variety of ways. Let's look at some of them and how they can be used in TypeScript programs.

    hashtag
    Basics

    To define an interface in TypeScript, use the interface keyword:

    This defines a type, Greetable, that has a member function called greet that takes a string argument. You can use this type in all the usual positions; for example in a parameter type annotation. Here we use that type annotation to get type safety on the g parameter:

    When this code compiles, you won't see any mention of Greetable in the JavaScript code. Interfaces are only a compile-time construct and have no effect on the generated code.

    hashtag
    Interfaces: TypeScript's Swiss Army Knife

    Interfaces get to play a lot of roles in TypeScript code. We'll go into more detail on these after a quick overview.

    Describing an Object

    Many JavaScript functions take a "settings object". For example, jQuery's $.ajax takes an object that can have up to several dozen members that control its behavior, but you're only likely to pass a few of those in any given instance. TypeScript interfaces allow optional properties to help you use these sorts of objects correctly.

    Describing an Indexable Object

    JavaScript freely mixes members (foo.x) with indexers (foo['x']), but most programmers use one or the other as a semantic hint about what kind of access is taking place. TypeScript interfaces can be used to represent what the expected type of an indexing operation is.

    Ensuring Class Instance Shape

    Often, you'll want to make sure that a class you're writing matches some existing surface area. This is how interfaces are used in more traditional OOP languages like C# and Java, and we'll see that TypeScript interfaces behave very similarly when used in this role.

    Ensuring the Static Shape of a Class or constructor Object

    Interfaces normally describe the shape of an instance of a class, but we can also use them to describe the static shape of the class (including its constructor function). We'll cover this in a later post.

    hashtag
    Describing an Object

    You can also use interfaces to define the shape of objects that will typically be expressed in an object literal. Here's an example:

    Describing Simple Types

    Note the use of the ? symbol after some of the names. This marks a member as being optional. This lets callers of createButton supply only the members they care about, while maintaining the constraint that the required parts of the object are present:

    You typically won't use optional members when defining interfaces that are going to be implemented by classes.

    Here's another example that shows an interesting feature of types in TypeScript:

    Note that we didn't annotate pt in any way to indicate that it's of type Point. We don't need to, because type checking in TypeScript is structural: types are considered identical if they have the same surface area. Because pt has at least the same members as Point, it's suitable for use wherever a Point is expected.

    Describing External Types

    Interfaces are also used to describe code that is present at runtime, but not implemented in the current TypeScript project. For example, if you open the lib.d.ts file that all TypeScript projects implicitly reference, you'll see an interface declaration for Number:

    Now if we have an expression of type Number, the compiler knows that it's valid to call toPrecision on that expression.

    Extending Existing Types

    Moreover, interfaces in TypeScript are open, meaning you can add your own members to an interface by simply writing another interface block. If you have an external script that adds members to Date, for example, you simply need to write interface Date { /*...*/ } and declare the additional members.*

    * Note: There are some known issues with the Visual Studio editor that currently prevent this scenario from working as intended. We'll be fixing this limitation in a later release.

    hashtag
    Describing an Indexable Object

    A common pattern in JavaScript is to use an object (e.g. {}) as way to map from a set of strings to a set of values. When those values are of the same type, you can use an interface to describe that indexing into an object always produces values of a certain type (in this case, Widget).

    hashtag
    Ensuring Class Instance Shape

    Let's extend the Greetable example above:

    We can implement this interface in a class using the implements keyword:

    Now we can use an instance of Person wherever a Greetable is expected:

    var g: Greetable = new Person();

    Similarly, we can take advantage of the structural typing of TypeScript to implement Greetable in an object literal:

    Tailwind CSS

    Tailwind CSS is a utility-based styling library. In order to streamline and standardize things like colors and spacing within our application, an Electron theme has been created to extend Tailwind's functionality, thus making it easy for us to access some standard Duke colors, fonts, etc. As a result, you will get most of the magic of Tailwind, but with most of the colors, text, and sizing options overwritten to reflect Duke's design system.

    We won't talk too much about the decisions behind why we are using this implementation here, but Chris Greufe has already done an incredible job documenting the ins-and-outs of it [here](https: //electron.duke-energy.com/foundation/utilities/utility-first). If you want to know more of the granular aspects and philosophy to our approach, you are encouraged to give it a read.

    Instead, this doc will mostly focus on how the dev team actually uses this implementation and a bit about our approach, as well as a proposed style guide.

    hashtag
    Philosophy and Approach

    Tailwind CSS is our primary way of styling within the DXT-JSS-Public React App. A lot of effort and resources have been put into making Tailwind and Electron do as much of the heavy lifting for us as possible, so for maintainability's sake, every effort should be made to find a solution to style your work with Tailwind. If you're hitting a wall trying to figure out an approach that works within Tailwind, don't hesitate to reach out to a teammate. If you find yourself in the rare situation where you encounter something that simply cannot be resolved using Tailwind, we use [Styled Components](https: //styled-components.com) as our fallback. If you find that you are creating a styled component often to deal with an edge case, it's probably worth documenting [here](https: //confluence.duke-energy.com/display/DEPW/Tailwind+requests).

    hashtag
    Tooling

    There's not a lot of tooling required for Tailwind but [the Tailwind CSS Intellisense VSCode plugin](https: //marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) can be pretty helpful. Once installed, it gives suggestions for Tailwind classes as well as Electron-specific ones. It also shows a preview of the CSS rules that the utility class utilizes.

    hashtag
    Enabling Tailwind Properties

    Although Tailwind provides the flexibility to apply a wide range of modifiers (called variants in Tailwind) to a variety of CSS rules, you may occasionally find that a Tailwind class is not behaving the way you expect in a responsive context, or upon hover. This may mean that you will need to edit the Tailwind config. [You can find more info about that here.](https: //tailwindcss.com/docs/configuring-variants) Please be careful to [extend the values](https: //v1.tailwindcss.com/docs/configuring-variants), rather than simply adding them to the variant object, which will overwrite all the defaults.

    hashtag
    Key Differences

    The main difference you'll find between Tailwind's approach and Electron's is that Duke doesn't need an extensive color library. As a result, you'll find that something like text-blue-800 or bg-yellow-200 does not behave as you'd expect. Most likely you will be looking for something like text-blue-dark or bg-yellow. So the color palette will be limited, and rather than a number to modify the color, there will either be no modifier, or a modifier of -light, -lighter, -dark, or -darker.

    hashtag
    Style Guide

    Because almost all of our styles exist within utility classes, there is often no need for traditional CSS classes to style a block. It's fairly unusual to need to add a class like wrapper or large BEM classes. Occasionally, you may need to add a class to make it easier for unit tests to search for a selector. In such a case, we suggest that you use a js- prefix, and that you place it at the beginning of your utility classes.

    example:

    Often, the amount of classes you need to style a complex element can be rather long. In this case, it is suggested that you group your classes conceptually. Since Tailwind is a mobile-first framework, it makes sense to start with "base" styles that will be present across all sizes of the component, immediately followed by the responsive counterparts, in ascending order (md, lg, xl, etc).

    Of the base styles, start with sizing (height, width, padding, margin) and other fiddly rules so that they are easily accessible to you and anyone who may need to maintain your code in the future. Utility classes that represent rules that are easily identifiable at a glance, such as text color or background color, should come secondary.

    That was a lot of words, so let's look at an example.

    🚫 Bad

    ✅ Good

    That may seem a lot to unpack, so let's examine that for a second. Note that the classes start with the width property and then are immediately followed by the responsive variant. This pattern follows as we move through the margin and into the padding, which is then followed by the display types, and onto more obvious styles like font and background colors, which don't require a check of the code in order to verify the values. Finally, the rules end with the transition and duration properties, which are often "set and forget" rules.

    hashtag
    Resources

    • [Styled Components](https: //styled-components.com) - documentation in case you need to step outside the Tailwind garden

    • [Tailwind Requests](https: //confluence.duke-energy.com/display/DEPW/Tailwind+requests) - add to the Tailwind/Electron wishlist!

    • [Electron Docs](https: //electron.duke-energy.com/foundation/utilities/utility-first)

    React | Typescript | Tailwind | Forms | Unit Tests

    Forms

    Dynamically generating forms with React and Sitecore JSS

    hashtag
    Creating the FormField

    1. Navigate to page http: //local.duke-energy.com:3000/home/products/outdoor-lighting/contact

      • This page calls the <SingleStepForm /> component in the layout json from Sitecore which have fields that look like this

    2. <SingleStepForm /> gets rendered via its composition file converts this JSS fields object as its props.

    3. <SingleStepForm /> checks to make sure that modelJson.value exists before parsing it from a string to actual json, creating the variable formModel.

      • fields.ModelJson ie: formModel contains data for every field and their props. It's an array of objects where each object contains information about that field. After parsing the JSS, formModel

    4. formModel then gets passed into createFormInit() to process all of this data into something more concise and manageable

    const createdFields = useMemo(() => createFormInit(formModel, false), []);

    • The formModel array will get passed into parseFields()

    • parseFields() will then run each of the form objects that are inside the formModel through yet another and final parser, createForm(). It will then save these objects to a new array and filter out any nulls generated by createForm()

    \

    hashtag
    Side note: Important info

    1. Since there is a lot of very important information missing from the fields.ModelJson, (input type, hidden fields, etc) from Sitecore we need a way to dynamically figure all of this out the best we can, with what we've been given. This is where mapping.ts comes in. This file contains 3 sections:

      • inputMap

    const inputMap: CFMappingType["inputMap"] = { alternatephone: "phone", "alternate_phone_#": "phone", captcha: "recaptcha", checkbox: "checkbox", checkbox_list: "checkboxGroup",

    // ... };

    • regexMap

      • regexMap is an object with either a validationPattern name or input type as the key with the value being an object containing an error message and RegEx pattern to be used in validation

    const regexMap: CFMappingType["regexMap"] = { email: { message: "Not a valid email format", value: /^[^\s@]+@[^\s@]+.[^\s@]{2,}$/, }, lettersWhiteSpace: { message: "Can only be letters and spaces", value: /^[a-zA-ZáéíóúüÁÉÍÓÚÜñÑ-]+(\s+[a-zA-ZáéíóúüÁÉÍÓÚÜñÑ]+)*$/, }, notSameDigits: { message: "Can only contain numbers", value: /^(\d)\d*(?!\1)\d+$/, },

    // ... };

    const { file, props } = dataMap[inputType];

    const Component = loadable(() => import(src/components/Form/${file}));

    • Component: The React component that was dynamically imported

    • data: An object containing details about the field such as maxLength, minLength, placeholder, required, etc..

    • file: The name of the component that was dynamically imported

    • id: A unique id for each field since sitecore doesn't return usable id's

    • props: The props returned from the dataMap lookup along with the column width for each field

    1. <SingleStepForm /> then iterates through the createdFields array and will:

      • pass the props.columns value to <FieldWrapper /> to determine how wide the field should be in CSS

    hashtag
    Inside the FormField

    1. Inside the /components/Form folder are all of the FormField components. These components are the default exports from each of these files such as <FormInput />

      • The purpose of these components is to take the createForm() generated props that were passed in via the <SingleStepForm /> and to 'normalize' them into simpler generic props for the <Input />

    const FormInput = ({ data, errors, name, props, register, validations, }: FormInputProps) => {

    const { mask, type } = props || {};

    const { label = "", maxLength, required, toolTipText } = data;

    const propData = { error: { hasError: Boolean(errors[name]?.message), errorMessage: errors[name]?.message, }, id: label, label, mask, maxLength, name, register, required, type, validations, };

    hashtag
    MultiStepForm

    The <MultiStepForm /> is nearly identical to <SingleStepForm /> except that it has multiple steps to the form. Like <SingleStepForm />, the fields data will also get pushed to createFormInit() and from there to multiStepFormFields(). It's here that the fields will be parsed through parsedFields() and then afterwards they will be separated into an array that contains the <FormStepper /> fields first and then the fields for the first screen, second screen and so on. This array will look something like this:

    When this array is finally returned back to <MultiStepForm>, the form stepper data is parsed out and passed into the <FormStepper /> component and the first array of field objects in the remaining array will be rendered out.

    We need to map through the outer array first and then loop through each of the inner array's to render each of the fields. The fields that are not shown on the current step will get a class added to FieldWrapper as hidden, all of the other fields will get the class, block. Since react-hook-form tracks our inputs via a ref, its much easier to just build out all of the steps of the form at once and just hide/show the active fields via CSS. This way we don't lose the ref when the fields get mounted/unmounted from the DOM.

    We also need to account for required fields that are now 'hidden' due to not being contained in the activeStep. This will cause an error in the browser saying that a required field is not focusable. We get around that by toggling the 'required' prop for that field depending on whether or not its currently in the activeStep

    React | Typescript | Tailwind | Forms | Unit Tests

    React

    hashtag
    Folder and File Structure

    Each component should have its own folder nested inside the /components folder. The name of this folder should match the name of the component itself. Inside this folder you should have the following files:

    • data.js

      • contains a copy of the JSS fields object for this component. This will get imported in unit tests and Storybook stories. If the component doesn't get called from Sitecore and doesn't have JSS fields, then this file isn't needed.

    • index.tsx

      • main React component

    • stories.js

      • Storybook stories

    • styles.ts(x) (optional)

      • if you use Styled Components for the component, they will live here

    • test.tsx

      • Jest unit tests

    • types.ts

      • Typescript types and interfaces the component

    \


    \

    hashtag
    Composition File

    Before working on the React component itself we first need to compose simpler props for it instead of parsing through the JSS fields object directly. The goal is to turn something like this:

    into this:

    The composition file is located at lib/composition/index.js and contains an exported function for each of the React components. The JSS fields object (and sometimes placeholder object) from Sitecore is passed in and destructured. Using the ComponentTypes global Typescript type both as a reference and consistent naming convention amongst all of the components, you will use these as keys to build your return object which will be passed in as the final props object to your component.

    In the following example, the QuickLinks component will get a single items prop returned to it, which will consist of an array of objects

    hashtag
    Optional Chaining

    An important thing to note and best practice is to use optional chaining when reaching into an object. The (?.) operator allows you to reach as far down into a nested object without first having to validate that the previous step in the chain is valid and exists first.

    [MDN - Optional Chaining](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)

    \


    \

    hashtag
    Function Components

    We should always use function components with hooks over class components. Function components are 'stateless' and simple as they don't track state or have lifecycle and render methods like class components do. If you need to track state in your component, import the useState hook.

    hashtag
    Use Short Syntax Over React Fragments

    If your return content doesn't have a wrapping div you will need to create a fragment by using a short syntax element <> rather than a React <Fragment>. The only exception is if you need to use a key when using a map. In these instances you will need to use a <Fragment> [React Short Syntax](https: //reactjs.org/docs/fragments.html#short-syntax)

    🚫 Bad

    ✅ Good

    ✅ Good

    hashtag
    Use Implicit Returns

    With ES6 and arrow functions you can omit the curly braces and the return keyword if your component is immediately returning something.

    🚫 Bad

    🚫 Bad

    ✅ Good

    ✅ Good

    hashtag
    Use PascalCase and Export At The End Of The File

    Component names should always be PascalCased and exported at the bottom of the file. If there is more than one component to export you need to declare which one is the default export:

    \


    \

    hashtag
    Spread and Destructure

    Where possible always destructure props and other objects/arrays where used. This will help in typing and keeping things clear and not hidden behind a root object. If you don't need to destructure out all of the properties of an object, use the ...rest keyword and pass that in instead like so:

    🚫 Bad

    ✅ Good

    If you're not transforming the props names from the parent to the child, then you can omit setting them directly on the child and instead just spread them in with the {...rest} property.

    \


    \

    hashtag
    Small, Focused and DRY

    Our components should be broken down into small, reusable functions instead of one gigantic component.

    🚫 Bad

    ✅ Good

    Each component should do only one thing and only be responsible for the logic for that one thing. If your function is growing in size, it's probably time to refactor it into smaller, more focused components. This will also help in making our components more reusable, consistent and TESTABLE.

    React | Typescript | Tailwind | Forms | Unit Tests

    Typescript

    hashtag
    Global types

    In typescript global types can be declared in a .d.ts file and used anywhere without explicitly importing them. Our project's .d.ts file is named project.d.ts.

    chevron-rightproject.d.tshashtag

    It contains:

    1. Some library types in the form of [triple slash directives](https: //www.typescriptlang.org/docs/handbook/triple-slash-directives.html). These need to be placed at the top of the file.

    2. Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).

    3. Our own global types.

    Typescript provides many [Utility Types](https: //www.typescriptlang.org/docs/handbook/utility-types.html) which are useful for manipulating the base types in the global ComponentTypes interface.

    chevron-rightUtility Typeshashtag

    TypeScript: Documentation - Utility Types

    Excerpt

    Types which are globally included in TypeScript


    TypeScript provides several utility types to facilitate common type transformations. These utilities are available globally.

    A few basic ones to know:

    hashtag
    Pick<Type, Keys>

    Only use the specified Keys from the Type.

    hashtag
    Partial<Type>

    Allows the type to be optional (undefined)

    hashtag
    Required<Type>

    Opposite of Partial, the type must be defined

    Using the stategies above you can select types from the global source and compose them to create a representation of the props in a specific component. While the global types live in project.d.ts, component level types should generally be placed in a types.ts file within the component directory and imported for use.

    Although ComponentTypes is a ✅ _Good starting place, some components may require a type that is more specific and not usefully included in the global declaration._


    hashtag
    Naming


    hashtag
    class

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers/methods use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    enum

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    interface

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    namespace

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    type

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ ✅ Good


    hashtag
    variable and function

    🐪 camelCase

    🚫 Bad

    ✅ Good


    React | Typescript | Tailwind | Forms | Unit Tests

    Typescript Types

    hashtag
    Everyday Types

    In this chapter, we'll cover some of the most common types of values you'll find in JavaScript code, and explain the corresponding ways to describe those types in TypeScript. This isn't an exhaustive list, and future chapters will describe more ways to name and use other types.

    Types can also appear in many more places than just type annotations. As we learn about the types themselves, we'll also learn about the places where we can refer to these types to form new constructs.

    We'll start by reviewing the most basic and common types you might encounter when writing JavaScript or TypeScript code. These will later form the core building blocks of more complex types.

    React Element Factories and JSX Warning

    You probably came here because your code is calling your component as a plain function call. This is now deprecated:

    hashtag
    JSX

    React components can no longer be called directly like this. Instead you can use JSX.

    Ideas

    [11:24 AM] Pope, Spencer O

    Heres the Search abstract,

    \

    There was an idea for some type of autocomplete, but it was omitted from the MVP version we launched with. Early step in re-visiting this would be to 1. check that this type of thing is still desirable, 2. research what is possible with the SOLR search platform

    \

    like 1

    Types VS Interfaces

    hashtag
    Types vs. interfaces in TypeScript - LogRocket Blog

    Excerpt

    It is very simple to get started with TypeScript, but sometimes we need to think more about the best use case for us. In this case, types or interfaces?


    Autocomplete:

    The HTML autocomplete attribute lets web developers specify what if any permission the has to provide automated assistance in filling out form field values, as well as guidance to the browser as to the type of information expected in the field.

    It is available on elements that take a text or numeric value as input, elements, elements, and elements.

    The source of the suggested values is generally up to the browser; typically values come from past values entered by the user, but they may also come from pre-configured values. For instance, a browser might let the user save their name, address, phone number, and email addresses for autocomplete purposes. Perhaps the browser offers the ability to save encrypted credit card information, for autocompletion following an authentication procedure.

    If an , or

    Don't Call PropTypes Warning

    Note:

    React.PropTypes has moved into a different package since React v15.5. Please use .

    We provide a codemod script to automate the conversion.

    In a future major release of React, the code that implements PropType validation functions will be stripped in production. Once this happens, any code that calls these functions manually (that isn't stripped in production) will throw an error.

    Focus Order

    hashtag
    Understanding Success Criterion 2.4.3: Focus Order

    Excerpt

    The intent of this Success Criterion is to ensure that when users navigate sequentially through content, they encounter information in an order that is consistent with the meaning of the content and can be operated from the keyboard. This reduces confusion by letting users form a consistent mental model of the content. There may be different orders that reflect logical relationships in the content. For example, moving through components in a table one row at a time or one column at a time both reflect the logical relationships in the content. Either order may satisfy this Success Criterion.

    Accessability

    Understanding Techniques for WCAG Success Criteria

    hashtag
    Introduction to Web Accessibility | Web Accessibility Initiative (WAI) | W3C

    Excerpt

    The Website of the World Wide Web Consortium’s Web Accessibility Initiative.


    Testing

    https: //testing-playground.com/gist/7c68f0970c4ad5e2e1342390f4dea70c/64c2420ce9169f440cb8810b7ce438f53c766cef

    • [A clear way to unit testing React JS components using Jest and React Testing Library](https: //www.richardkotze.com/coding/react-testing-library-jest) by [Richard Kotze](https: //github.com/rkotze)

    • [Integration testing in React](https: //medium.com/@jeffreyrussom/integration-testing-in-react-21f92a55a894) by [Jeffrey Russom](https: //github.com/qswitcher)

    ARIA - Accessibility

    hashtag

    Excerpt

    The aria-labelledby attribute identifies the element (or elements) that labels the element it is applied to.


    Accessible Rich Internet Applications (ARIA)

    The idea of having static type-checking in JavaScript is really fantastic and the adoption of TypeScript is growing more every day.

    You started to use TypeScript in your project, you created your first type, then you jumped to your first interface, and you got it working. You concluded that TypeScript, in fact, was helping your development and saving you precious time, but you might have made some mistakes and not followed the best practices when you started to work with types and interfaces in TypeScript.

    This is the case for a lot of developers, they don't really know the real difference between type aliases and interfaces in TypeScript.

    It is very simple to get started with TypeScript, but sometimes we need to think more about the best use case for us. In this case, types or interfaces?

    hashtag
    Types and type aliases

    Before we jump into the differences between types and interfaces in TypeScript, we need to understand something.

    In TypeScript, we have a lot of basic types, such as string, boolean, and number. These are the basic types of TypeScript. You can check the list of all the basic types [here](https: //www.typescriptlang.org/docs/handbook/basic-types.html#table-of-contents). Also, in TypeScript, we have advanced types and in these [advanced types](https: //www.typescriptlang.org/docs/handbook/advanced-types.html), we have something called [type aliases](https: //www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases). With type aliases, we can create a new name for a type but we don't define a new type.

    We use the type keyword to create a new type alias, that's why some people might get confused and think that it's creating a new type when they're only creating a new name for a type. So, when you hear someone talking about the differences between types and interfaces, like in this article, you can assume that this person is talking about type aliases vs interfaces.

    We will use the [TypeScript Playground](https: //www.typescriptlang.org/play/index.html#) for code examples. The [TypeScript Playground](https: //www.typescriptlang.org/play/index.html#) allows us to work and test the latest version of TypeScript (or any other version we want to), and we will save time by using this playground rather than creating a new TypeScript project just for examples.

    hashtag
    Types vs. interfaces

    The difference between types and interfaces in TypeScript used to be more clear, but with the latest versions of TypeScript, they're becoming more similar.

    Interfaces are basically a way to describe data shapes, for example, an object.

    Type is a definition of a type of data, for example, a union, primitive, intersection, tuple, or any other type.

    hashtag
    Declaration merging

    One thing that's possible to do with interfaces but are not with types is declaration merging. Declaration merging happens when the TypeScript compiler merges two or more interfaces that share the same name into only one declaration.

    Let's imagine that we have two interfaces called Song, with different properties:

    interface Song { artistName: string; }; interface Song { songName: string; }; const song: Song = { artistName: "Freddie", songName: "The Chain" };

    TypeScript will automatically merge both interfaces declarations into one, so when we use this Song interface, we'll have both properties.

    Declaration merging does not work with types. If we try to create two types with the same names, but with different properties, TypeScript would still throw us an error.

    Duplicate identifier Song.

    hashtag
    Extends and implements

    In TypeScript, we can easily extend and implement interfaces. This is not possible with types though.

    Interfaces in TypeScript can extend classes, this is a very awesome concept that helps a lot in a more object-oriented way of programming. We can also create classes implementing interfaces.

    For example, let's imagine that we have a class called Car and an interface called NewCar, we can easily extend this class using an interface:

    class Car { printCar = () => { console.log("this is my car") } }; interface NewCar extends Car { name: string; }; class NewestCar implements NewCar { name: "Car"; constructor(engine:string) { this.name = name } printCar = () => { console.log("this is my car") } };

    hashtag
    Intersection

    Intersection allows us to combine multiple types into a single one type. To create an intersection type, we have to use the & keyword:

    type Name = { name: "string" }; type Age = { age: number }; type Person = Name & Age;

    The nice thing here is that we can create a new intersection type combining two interfaces, for example, but not the other way around. We cannot create an interface combining two types, because it doesn't work:

    interface Name { name: "string" }; interface Age { age: number }; type Person = Name & Age;

    hashtag
    Unions

    Union types allow us to create a new type that can have a value of one or a few more types. To create a union type, we have to use the | keyword.

    type Man = { name: "string" }; type Woman = { name: "string" }; type Person = Man | Woman;

    Similar to intersections, we can create a new union type combining two interfaces, for example, but not the other way around:

    interface Man { name: "string" }; interface Woman { name: "string" }; type Person = Man | Woman;

    hashtag
    Tuples

    [Tuples](https: //www.typescriptlang.org/docs/handbook/basic-types.html#tuple) are a very helpful concept in TypeScript, it brought to us this new data type that includes two sets of values of different data types.

    type Reponse = [string, number]

    But, in TypeScript, we can only declare tuples using types and not interfaces. There's no way we can declare a tuple in TypeScript using an interface, but you still are able to use a tuple inside an interface, like this:

    interface Response { value: [string, number] }

    We can see that we can achieve the same result as using types with interfaces. So, here comes the question that a lot of developers might have — should I use a type instead of an interface? If so, when should I use a type?

    Let's understand the best use cases for both of them, so you don't need to abandon one for the other.

    hashtag
    What should I use?

    This question is really tricky, and the answer to it, you might guess, depends on what you're building and what you're working on.

    Interfaces are better when you need to define a new object or method of an object. For example, in React applications, when you need to define the props that a specific component is going to receive, it's ideal to use interface over types:

    interface TodoProps { name: string; isCompleted: boolean }; const Todo: React.FC<TodoProps> = ({ name, isCompleted }) => { ... };

    Types are better when you need to create functions, for example. Let's imagine that we have a function that's going to return an object called, type alias is more recommended for this approach:

    type Person = { name: string, age: number }; type ReturnPerson = ( person: Person ) => Person; const returnPerson: ReturnPerson = (person) => { return person; };

    At the end of the day, to decide if you should use a type alias or an interface, you should carefully think and analyze the situation — what you're working on, the specific code, etc.

    Interface work better with objects and method objects, and types are better to work with functions, complex types, etc.

    You should not start to use one and delete the other. Instead of doing that, start to refactor slowly, thinking of what makes more sense to that specific situation.

    Remember that you can use both together and they will work fine. The idea here is just to clarify the differences between types and interfaces, and the best use cases for both.

    hashtag
    Conclusion

    In this article, we learned more about the differences between types and interfaces in TypeScript. We learned that type aliases are advanced types in TypeScript, we learned the best use cases for both types and interfaces in TypeScript, and how we can apply both of them in real projects.

    hashtag

    [React Testing Library have fantastic testing 🐐](https: //medium.com/yazanaabed/react-testing-library-have-a-fantastic-testing-198b04699237) by [Yazan Aabed](https: //github.com/YazanAabeed)

  • [Building a React Tooltip Library](https: //www.youtube.com/playlist?list=PLMV09mSPNaQmFLPyrfFtpUdClVfutjF5G) by [divyanshu013](https: //github.com/divyanshu013) and [metagrover](https: //github.com/metagrover)

  • [A sample repo using React Testing Library to test a Relay Modern GraphQL app](https: //github.com/zth/relay-modern-flow-jest-example)

  • [Creating Readable Tests Using React Testing Library](https: //medium.com/flatiron-labs/creating-readable-tests-using-react-testing-library-2bd03c49c284) by [Lukeghenco](https: //github.com/Lukeghenco)

  • [My Experience moving from Enzyme to React Testing Library](https: //medium.com/@boyney123/my-experience-moving-from-enzyme-to-react-testing-library-5ac65d992ce) by [David Boyne](https: //github.com/boyney123)

  • [Testing Formik with React Testing Library](https: //scottsauber.com/2019/05/25/testing-formik-with-react-testing-library/) by [Scott Sauber](https: //github.com/scottsauber)

  • [How to Test Asynchronous Methods](https: //www.polvara.me/posts/how-to-test-asynchronous-methods/) by [Gpx](https: //twitter.com/Gpx)

  • [Writing better tests with react-testing-library](https: //www.youtube.com/watch?v=O0VxvRqgm7g) by [Billy Mathews](https: //twitter.com/BillRMathews)

  • [React Hooks broke my tests, now what?](https: //youtu.be/p3WS9GmfX_Q) by [Daniel Afonso](https: //twitter.com/danieljcafonso)

  • [Testing Apollo Components Using react-testing-library](https: //www.arahansen.com/testing-apollo-components-using-react-testing-library/) by [Andrew Hansen](https: //twitter.com/arahansen)

  • Abstract

    https://app.abstract.com/projects/7d33aa49-f1f0-47eb-971d-893d6457bcbc/branches/0767f146-cbd8-44a0-bead-df98f3ec5686/commits/0a02778ae31421c50d7ba31db8377c52fdb13235/files/56AED96A-C786-42FD-8B75-CFA17F1BE644/layers/06C2670A-54B1-486E-831D-83A2DB721228?collectionId=35c0616f-fc52-416c-82a1-b8593f550d80&collectionLayerId=1183c086-463d-4b2a-8d19-926f35914b0b&mode=designarrow-up-right

    \

    https://app.abstract.com/projects/ab3d66ba-f90d-437a-a2be-c3f0d7144933/branches/master/commits/12d33dccde490d0aaf31b2380ee3734eb2ed5deb/files/c0563358-566e-463f-9b32-fcfe73c25835/layers/D7677409-1CCF-4758-8431-81A7F657946Carrow-up-right

    [Tailwind VSCode Intellisense Plugin](https: //marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)

  • [Nerdcave CheatSheet](https: //nerdcave.com/tailwind-cheat-sheet) - some random guy made a really handy cheatsheet for Tailwind CSS. Obviously, our rules won't be on it, but it's a nice quick reference for a lot of the classes.

  • [Tailwind CSS Docs](https: //tailwindcss.com/docs) - their official docs are better than most

  • [Configuring Variants](https: //v1.tailwindcss.com/docs/configuring-variants) - case in point

  • hashtag
    The primitives:string,number, andboolean

    JavaScript has three very commonly used [primitives](https: //developer.mozilla.org/en-US/docs/Glossary/Primitive): string, number, and boolean. Each has a corresponding type in TypeScript. As you might expect, these are the same names you'd see if you used the JavaScript typeof operator on a value of those types:

    • string represents string values like "Hello, world"

    • number is for numbers like 42. JavaScript does not have a special runtime value for integers, so there's no equivalent to int or float - everything is simply number

    • boolean is for the two values true and false

    The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.

    hashtag
    Arrays

    To specify the type of an array like [1, 2, 3], you can use the syntax number[]; this syntax works for any type (e.g. string[] is an array of strings, and so on). You may also see this written as Array<number>, which means the same thing. We'll learn more about the syntax T<U> when we cover generics.

    Note that [number] is a different thing; refer to the section on [Tuples](https: //www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types).

    hashtag
    any

    TypeScript also has a special type, any, that you can use whenever you don't want a particular value to cause typechecking errors.

    When a value is of type any, you can access any properties of it (which will in turn be of type any), call it like a function, assign it to (or from) a value of any type, or pretty much anything else that's syntactically legal:

    The any type is useful when you don't want to write out a long type just to convince TypeScript that a particular line of code is okay.

    hashtag
    noImplicitAny

    When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default to any.

    You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag [noImplicitAny](https: //www.typescriptlang.org/tsconfig#noImplicitAny) to flag any implicit any as an error.

    hashtag
    Type Annotations on Variables

    When you declare a variable using const, var, or let, you can optionally add a type annotation to explicitly specify the type of the variable:

    TypeScript doesn't use "types on the left"-style declarations like int x = 0; Type annotations will always go after the thing being typed.

    In most cases, though, this isn't needed. Wherever possible, TypeScript tries to automatically infer the types in your code. For example, the type of a variable is inferred based on the type of its initializer:

    For the most part you don't need to explicitly learn the rules of inference. If you're starting out, try using fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully understand what's going on.

    hashtag
    Functions

    Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the types of both the input and output values of functions.

    hashtag
    Parameter Type Annotations

    When you declare a function, you can add type annotations after each parameter to declare what types of parameters the function accepts. Parameter type annotations go after the parameter name:

    When a parameter has a type annotation, arguments to that function will be checked:

    Even if you don't have type annotations on your parameters, TypeScript will still check that you passed the right number of arguments.

    hashtag
    Return Type Annotations

    You can also add return type annotations. Return type annotations appear after the parameter list:

    Much like variable type annotations, you usually don't need a return type annotation because TypeScript will infer the function's return type based on its return statements. The type annotation in the above example doesn't change anything. Some codebases will explicitly specify a return type for documentation purposes, to prevent accidental changes, or just for personal preference.

    hashtag
    Anonymous Functions

    Anonymous functions are a little bit different from function declarations. When a function appears in a place where TypeScript can determine how it's going to be called, the parameters of that function are automatically given types.

    Here's an example:

    Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach function, along with the inferred type of the array, to determine the type s will have.

    This process is called contextual typing because the context that the function occurred within informs what type it should have.

    Similar to the inference rules, you don't need to explicitly learn how this happens, but understanding that it does happen can help you notice when type annotations aren't needed. Later, we'll see more examples of how the context that a value occurs in can affect its type.

    hashtag
    Object Types

    Apart from primitives, the most common sort of type you'll encounter is an object type. This refers to any JavaScript value with properties, which is almost all of them! To define an object type, we simply list its properties and their types.

    For example, here's a function that takes a point-like object:

    Here, we annotated the parameter with a type with two properties - x and y - which are both of type number. You can use , or ; to separate the properties, and the last separator is optional either way.

    The type part of each property is also optional. If you don't specify a type, it will be assumed to be any.

    hashtag
    Optional Properties

    Object types can also specify that some or all of their properties are optional. To do this, add a ? after the property name:

    In JavaScript, if you access a property that doesn't exist, you'll get the value undefined rather than a runtime error. Because of this, when you read from an optional property, you'll have to check for undefined before using it.

    hashtag
    Union Types

    TypeScript's type system allows you to build new types out of existing ones using a large variety of operators. Now that we know how to write a few types, it's time to start combining them in interesting ways.

    hashtag
    Defining a Union Type

    The first way to combine types you might see is a union type. A union type is a type formed from two or more other types, representing values that may be any one of those types. We refer to each of these types as the union's members.

    Let's write a function that can operate on strings or numbers:

    hashtag
    Working with Union Types

    It's easy to provide a value matching a union type - simply provide a type matching any of the union's members. If you have a value of a union type, how do you work with it?

    TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union string | number, you can't use methods that are only available on string:

    The solution is to narrow the union with code, the same as you would in JavaScript without type annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

    For example, TypeScript knows that only a string value will have a typeof value "string":

    Another example is to use a function like Array.isArray:

    Notice that in the else branch, we don't need to do anything special - if x wasn't a string[], then it must have been a string.

    Sometimes you'll have a union where all the members have something in common. For example, both arrays and strings have a slice method. If every member in a union has a property in common, you can use that property without narrowing:

    It might be confusing that a union of types appears to have the intersection of those types' properties. This is not an accident - the name union comes from type theory. The union number | string is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.

    hashtag
    Type Aliases

    We've been using object types and union types by writing them directly in type annotations. This is convenient, but it's common to want to use the same type more than once and refer to it by a single name.

    A type alias is exactly that - a name for any type. The syntax for a type alias is:

    You can actually use a type alias to give a name to any type at all, not just an object type. For example, a type alias can name a union type:

    Note that aliases are only aliases - you cannot use type aliases to create different/distinct "versions" of the same type. When you use the alias, it's exactly as if you had written the aliased type. In other words, this code might look illegal, but is OK according to TypeScript because both types are aliases for the same type:

    hashtag
    Interfaces

    An interface declaration is another way to name an object type:

    Just like when we used a type alias above, the example works just as if we had used an anonymous object type. TypeScript is only concerned with the structure of the value we passed to printCoord - it only cares that it has the expected properties. Being concerned only with the structure and capabilities of types is why we call TypeScript a structurally typed type system.

    hashtag
    Differences Between Type Aliases and Interfaces

    Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

    Interface

    Type

    |

    Extending an interface

    |

    Extending a type via intersections

    | |

    Adding new fields to an existing interface

    |

    A type cannot be changed after being created

    |

    You'll learn more about these concepts in later chapters, so don't worry if you don't understand all of these right away.

    • Prior to TypeScript version 4.2, type alias names [may appear in error messages](https: //www.typescriptlang.org/play?#code/PTAEGEHsFsAcEsA2BTATqNrLusgzngIYDm+oA7koqIYuYQJ56gCueyoAUCKAC4AWHAHaFcoSADMaQ0PCG80EwgGNkALk6c5C1EtWgAsqOi1QAb06groEbjWg8vVHOKcAvpokshy3vEgyyMr8kEbQJogAFND2YREAlOaW1soBeJAoAHSIkMTRmbbI8e6aPMiZxJmgACqCGKhY6ABGyDnkFFQ0dIzMbBwCwqIccabcYLyQoKjIEmh8kwN8DLAc5PzwwbLMyAAeK77IACYaQSEjUWZWhfYAjABMAMwALA+gbsVjoADqgjKESytQPxCHghAByXigYgBfr8LAsYj8aQMUASbDQcRSExCeCwFiIQh+AKfAYyBiQFgOPyIaikSGLQo0Zj-aazaY+dSaXjLDgAGXgAC9CKhDqAALxJaw2Ib2RzOISuDycLw+ImBYKQflCkWRRD2LXCw6JCxS1JCdJZHJ5RAFIbFJU8ADKC3WzEcnVZaGYE1ABpFnFOmsFhsil2uoHuzwArO9SmAAEIsSFrZB-GgAjjA5gtVN8VCEc1o1C4Q4AGlR2AwO1EsBQoAAbvB-gJ4HhPgB5aDwem-Ph1TCV3AEEirTp4ELtRbTPD4vwKjOfAuioSQHuDXBcnmgACC+eCONFEs73YAPGGZVT5cRyyhiHh7AAON7lsG3vBggB8XGV3l8-nVISOgghxoLq9i7io-AHsayRWGaFrlFauq2rg9qaIGQHwCBqChtKdgRo8TxRjeyB3o+7xAA), sometimes in place of the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in error messages.

    • Type aliases may not participate [in declaration merging, but interfaces can](https: //www.typescriptlang.org/play?#code/PTAEEEDtQS0gXApgJwGYEMDGjSfdAIx2UQFoB7AB0UkQBMAoEUfO0Wgd1ADd0AbAK6IAzizp16ALgYM4SNFhwBZdAFtV-UAG8GoPaADmNAcMmhh8ZHAMMAvjLkoM2UCvWad+0ARL0A-GYWVpA29gyY5JAWLJAwGnxmbvGgALzauvpGkCZmAEQAjABMAMwALLkANBl6zABi6DB8okR4Jjg+iPSgABboovDk3jjo5pbW1d6+dGb5djLwAJ7UoABKiJTwjThpnpnGpqPBoTLMAJrkArj4kOTwYmycPOhW6AR8IrDQ8N04wmo4HHQCwYi2Waw2W1S6S8HX8gTGITsQA).

    • Interfaces may only be used to [declare the shapes of objects, not rename primitives](https: //www.typescriptlang.org/play?#code/PTAEAkFMCdIcgM6gC4HcD2pIA8CGBbABwBtIl0AzUAKBFAFcEBLAOwHMUBPQs0XFgCahWyGBVwBjMrTDJMAshOhMARpD4tQ6FQCtIE5DWoixk9QEEWAeV37kARlABvaqDegAbrmL1IALlAEZGV2agBfampkbgtrWwMAJlAAXmdXdy8ff0Dg1jZwyLoAVWZ2Lh5QVHUJflAlSFxROsY5fFAWAmk6CnRoLGwmILzQQmV8JmQmDzI-SOiKgGV+CaYAL0gBBdyy1KCQ-Pn1AFFplgA5enw1PtSWS+vCsAAVAAtB4QQWOEMKBuYVUiVCYvYQsUTQcRSBDGMGmKSgAAa-VEgiQe2GLgKQA).

    • Interface names will [always appear in their original form](https: //www.typescriptlang.org/play?#code/PTAEGEHsFsAcEsA2BTATqNrLusgzngIYDm+oA7koqIYuYQJ56gCueyoAUCKAC4AWHAHaFcoSADMaQ0PCG80EwgGNkALk6c5C1EtWgAsqOi1QAb06groEbjWg8vVHOKcAvpokshy3vEgyyMr8kEbQJogAFND2YREAlOaW1soBeJAoAHSIkMTRmbbI8e6aPMiZxJmgACqCGKhY6ABGyDnkFFQ0dIzMbBwCwqIccabcYLyQoKjIEmh8kwN8DLAc5PzwwbLMyAAeK77IACYaQSEjUWY2Q-YAjABMAMwALA+gbsVjNXW8yxySoAADaAA0CCaZbPh1XYqXgOIY0ZgmcK0AA0nyaLFhhGY8F4AHJmEJILCWsgZId4NNfIgGFdcIcUTVfgBlZTOWC8T7kAJ42G4eT+GS42QyRaYbCgXAEEguTzeXyCjDBSAAQSE8Ai0Xsl0K9kcziExDeiQs1lAqSE6SyOTy0AKQ2KHk4p1V6s1OuuoHuzwArMagA) in error messages, but only when they are used by name.

    For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type.

    hashtag
    Type Assertions

    Sometimes you will have information about the type of a value that TypeScript can't know about.

    For example, if you're using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.

    In this situation, you can use a type assertion to specify a more specific type:

    Like a type annotation, type assertions are removed by the compiler and won't affect the runtime behavior of your code.

    You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:

    Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won't be an exception or null generated if the type assertion is wrong.

    TypeScript only allows type assertions which convert to a more specific or less specific version of a type. This rule prevents "impossible" coercions like:

    Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid. If this happens, you can use two assertions, first to any (or unknown, which we'll introduce later), then to the desired type:

    hashtag
    Literal Types

    In addition to the general types string and number, we can refer to specific strings and numbers in type positions.

    One way to think about this is to consider how JavaScript comes with different ways to declare a variable. Both var and let allow for changing what is held inside the variable, and const does not. This is reflected in how TypeScript creates types for literals.

    By themselves, literal types aren't very valuable:

    It's not much use to have a variable that can only have one value!

    But by combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:

    Numeric literal types work the same way:

    Of course, you can combine these with non-literal types:

    There's one more kind of literal type: boolean literals. There are only two boolean literal types, and as you might guess, they are the types true and false. The type boolean itself is actually just an alias for the union true | false.

    hashtag
    Literal Inference

    When you initialize a variable with an object, TypeScript assumes that the properties of that object might change values later. For example, if you wrote code like this:

    TypeScript doesn't assume the assignment of 1 to a field which previously had 0 is an error. Another way of saying this is that obj.counter must have the type number, not 0, because types are used to determine both reading and writing behavior.

    The same applies to strings:

    In the above example req.method is inferred to be string, not "GET". Because code can be evaluated between the creation of req and the call of handleRequest which could assign a new string like "GUESS" to req.method, TypeScript considers this code to have an error.

    There are two ways to work around this.

    1. You can change the inference by adding a type assertion in either location:

    // Change 1: const req = { url: "https: //example.com", method: "GET" as "GET" }; // Change 2handleRequest(req.url, req.method as "GET");Try

    The as const suffix acts like const but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string or number.

    hashtag
    nullandundefined

    JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.

    TypeScript has two corresponding types by the same names. How these types behave depends on whether you have the [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) option on.

    hashtag
    strictNullChecksoff

    With [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) off, values that might be null or undefined can still be accessed normally, and the values null and undefined can be assigned to a property of any type. This is similar to how languages without null checks (e.g. C#, Java) behave. The lack of checking for these values tends to be a major source of bugs; we always recommend people turn [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) on if it's practical to do so in their codebase.

    hashtag
    strictNullCheckson

    With [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) on, when a value is null or undefined, you will need to test for those values before using methods or properties on that value. Just like checking for undefined before using an optional property, we can use narrowing to check for values that might be null:

    hashtag
    Non-null Assertion Operator (Postfix!)

    TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing ! after any expression is effectively a type assertion that the value isn't null or undefined:

    Just like other type assertions, this doesn't change the runtime behavior of your code, so it's important to only use ! when you know that the value can't be null or undefined.

    hashtag
    Enums

    Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition to JavaScript but something added to the language and runtime. Because of this, it's a feature which you should know exists, but maybe hold off on using unless you are sure. You can read more about enums in the [Enum reference page](https: //www.typescriptlang.org/docs/handbook/enums.html).

    hashtag
    Less Common Primitives

    It's worth mentioning the rest of the primitives in JavaScript which are represented in the type system. Though we will not go into depth here.

    bigint

    From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt:

    You can learn more about BigInt in [the TypeScript 3.2 release notes](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#bigint).

    symbol

    There is a primitive in JavaScript used to create a globally unique reference via the function Symbol():

    You can learn more about them in [Symbols reference page](https: //www.typescriptlang.org/docs/handbook/symbols.html).

    hashtag
    Without JSX

    If you don't want to, or can't use JSX, then you'll need to wrap your component in a factory before calling it:

    This is an easy upgrade path if you have a lot of existing function calls.

    hashtag
    Dynamic components without JSX

    If you get a component class from a dynamic source, then it might be unnecessary to create a factory that you immediately invoke. Instead you can just create your element inline:

    hashtag
    In Depth

    Read more about WHY we're making this change.arrow-up-right

    var MyComponent = require("MyComponent");
    
    function render() {
      return MyComponent({ foo: "bar" }); // WARNING
    }
    hashtag
    Declaring PropTypes is still fine

    The normal usage of PropTypes is still supported:

    Nothing changes here.

    hashtag
    Don’t call PropTypes directly

    Using PropTypes in any other way than annotating React components with them is no longer supported:

    If you depend on using PropTypes like this, we encourage you to use or create a fork of PropTypes (such as thesearrow-up-right twoarrow-up-right packages).

    If you don't fix the warning, this code will crash in production with React 16.

    hashtag
    If you don't call PropTypes directly but still get the warning

    Inspect the stack trace produced by the warning. You will find the component definition responsible for the PropTypes direct call. Most likely, the issue is due to third-party PropTypes that wrap React’s PropTypes, for example:

    In this case, ThirdPartyPropTypes.deprecated is a wrapper calling PropTypes.bool. This pattern by itself is fine, but triggers a false positive because React thinks you are calling PropTypes directly. The next section explains how to fix this problem for a library implementing something like ThirdPartyPropTypes. If it's not a library you wrote, you can file an issue against it.

    hashtag
    Fixing the false positive in third party PropTypes

    If you are an author of a third party PropTypes library and you let consumers wrap existing React PropTypes, they might start seeing this warning coming from your library. This happens because React doesn't see a "secret" last argument that it passesarrow-up-right to detect manual PropTypes calls.

    Here is how to fix it. We will use deprecated from react-bootstrap/react-prop-typesarrow-up-right as an example. The current implementation only passes down the props, propName, and componentName arguments:

    In order to fix the false positive, make sure you pass all arguments down to the wrapped PropType. This is easy to do with the ES6 ...rest notation:

    This will silence the warning.

    the prop-types library insteadarrow-up-right
    is a set of attributes that define ways to make web content and web applications (especially those developed with JavaScript) more accessible to people with disabilities.

    It supplements HTML so that interactions and widgets commonly used in applications can be passed to assistive technologies when there is not otherwise a mechanism. For example, ARIA enables accessible JavaScript widgets, form hints and error messages, live content updates, and more.

    Warning: Many of these widgets were later incorporated into HTML5, and developers should prefer using the correct semantic HTML element over using ARIA, if such an element exists. For instance, native elements have built-in keyboard accessibilityarrow-up-right, roles and states. However, if you choose to use ARIA, you are responsible for mimicking the equivalent browser behavior in script.

    The first rule of ARIAarrow-up-right use is "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so."

    Note: There is a saying "No ARIA is better than bad ARIA." In WebAim's survey of over one million home pagesarrow-up-right, they found that Home pages with ARIA present averaged 41% more detected errors than those without ARIA. While ARIA is designed to make web pages more accessible, if used incorrectly, it can do more harm than good.

    Here's the markup for a progress bar widget:

    This progress bar is built using a <div>arrow-up-right, which has no meaning. We include ARIA roles and properties to add meaning. In this example, the role="progressbar"arrow-up-right attribute informs the browser that this element is actually a JavaScript-powered progress bar widget. The aria-valueminarrow-up-right and aria-valuemaxarrow-up-right attributes specify the minimum and maximum values for the progress bar, and the aria-valuenowarrow-up-right describes the current state of it and therefore must be kept updated with JavaScript.

    Along with placing them directly in the markup, ARIA attributes can be added to the element and updated dynamically using JavaScript code like this:

    All content that is available to non-assistive technology users must be made available to assistive technologies. Similarly, no features should be included targeting assistive technology users that aren't also accessible to those not using assistive technologies. The above progressbar needs to be styled to make it look like a progressbar.

    It would have been much simpler to use the native <progress>arrow-up-right element instead:

    Note: The min attribute is not allowed for the <progress>arrow-up-right element; its minimum value is always 0.

    Note: HTML landmark elements (<main>arrow-up-right, <header>arrow-up-right, <nav>arrow-up-right etc.) have built-in implicit ARIA roles, so there is no need to duplicate them.

    Like any other web technology, there are varying degrees of support for ARIA. Support is based on the operating system and browser being used, as well as the kind of assistive technology interfacing with it. In addition, the version of the operating system, browser, and assistive technology are contributing factors. Older software versions may not support certain ARIA roles, have only partial support, or misreport its functionality.

    It is also important to acknowledge that some people who rely on assistive technology are reluctant to upgrade their software, for fear of losing the ability to interact with their computer and browser. Because of this, it is important to use semantic HTML elementsarrow-up-right whenever possible, as semantic HTML has far better support for assistive technology.

    It is also important to test your authored ARIA with actual assistive technology. Much as how browser emulators and simulators are not an effective solution for testing full support, proxy assistive technology solutions aren't sufficient to fully guarantee functionality.

    Pick<ComponentTypes, "text">;
    
    // only use 'text' type
    Partial<Pick<ComponentTypes, "text">>;
    
    // only use 'text' type
    
    // the text type is optional
    Required<Pick<ComponentTypes, "text">>;
    
    // only use 'text' type
    
    // the text type is required
    class foo {}
    class Foo {}
    class Foo {
      Bar: number;
      BazQux() {}
    }
    class Foo {
      bar: number;
      bazQux() {}
    }
    enum backgroundColor {}
    enum BackgroundColor {}
    interface checkboxProps {}
    interface CheckboxProps {}
    interface CheckboxProps {
      IsSelected: boolean;
    }
    interface CheckboxProps = {
      isSelected: boolean;
    }
    namespace foo {}
    namespace Foo {}
    type imageProps = { src: string; alt: string };
    type ImageProps = { src: string; alt: string };
    const FooBar = "baz";
    
    const FooBar = () => "baz";
    const fooBar = "baz";
    
    const fooBar = () => "baz";
    interface Greetable {    greet(message: string): void;}
    function helloEnglish(g: Greetable) {    g.greet('Hello there!');
    // OK    g.greet(42);
    // Not OK -- 42 is not a string    g.greep('Hi');
    // Not OK -- 'greep' is not a member of 'Greetable'}
    interface ButtonSettings {    text: string;    size?: { width: number; height: number; };    color?: string;}function createButton(settings: ButtonSettings) { ... }
    createButton({ text: "Submit" });
    // OKcreateButton({ text: 'Submit', size: { width: 70, height: 30 }});
    // OKcreateButton({ text: 'Submit', color: 43);
    // Not OK: 43 isn't a stringcreateButton({ text: 'Submit', size: { width: 70 });
    // Not OK: size needs a height as wellcreateButton({ color: 'Blue'});
    // Not OK: 'text' member is required
    interface Point {    x: number;    y: number;}function getQuadrant(pt: Point) { ... }var pt = { x: 0, y: -1 };getQuadrant(pt);
    // OK: pt has members x and y of type number
    interface Number {    toString(radix?: number): string;    toFixed(fractionDigits?: number): string;    toExponential(fractionDigits?: number): string;    toPrecision(precision: number): string;}
    interface WidgetMap {    [name: string]: Widget;}var map: WidgetMap = {};map['gear'] = new GearWidget();var w = map['gear'];
    // w is inferred to type Widget
    /** Represents an object that can be greeted */interface Greetable {    /** Used to welcome someone */    greet(message: string): void;    /** The preferred language of this object */    language: string;}
    class Person implements Greetable {    language = 'English';    greet(message: string) {        console.log(message);    }}
    var greeter = {    greet: (message: string) => { console.log(message) };    language: 'Any';};
    <div className="js-form text-blue-dark..." />
    <div className="text-blue transition-all md:px-32 mt-12 flex bg-black w-20 duration-500 md:block px-24 lg:w-16 lg:px-48" />
    <div className="w-20 lg:w-16 mt-12 px-24 md:px-32 lg:px-48 flex md:block text-blue bg-black transition-all duration-500 />
    <Component title={fields.PodItemLink?.value?.text} />
    <Component title={title} />
    // composition/index.js
    
    const QuickLinks = ({ fields }) => {
      const items = fields?.QuickLinkItems?.reduce(
        (acc, curr) => [
          ...acc,
          {
            image: { ...curr.Icon?.value },
            link: curr.Link?.value?.href,
            title: curr.Title?.value,
            id: curr.Id,
          },
        ],
        []
      );
      return { items };
    };
    // components/QuickLinks/index.tsx
    
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    import React, { useState } from "react";
    
    const Button = () => {
      const [count, setCount] = useState(0);
      return <button onClick={setCount(count + 1)}>clicked {count} times!</button>;
    };
    const ChildComponent = () => {
      return (
        <Fragment>
          <div>Title</div>
          <div>SubTitle</div>
        </Fragment>
      );
    };
    const ChildComponent = () => {
      return (
        <>
          <div>Title</div>
          <div>SubTitle</div>
        </>
      );
    };
    const Component = ({ items }: { items: Array<ComponentTypes> }) =>
      items?.map(({ id, ...rest }, index) => (
        <Fragment key={id}>
          <div>The title</div>
          <InnerComponent index={id} length={items.length} {...rest} />
        </Fragment>
      ));
    const ChildComponent = () => {
      return <div>The Child</div>;
    };
    const ChildComponent = () => {
      return (
        <div>
          <Component />
          <Component />
          <Component />
        </div>
      );
    };
    const ChildComponent = () => <div>The Child</div>;
    const ChildComponent = () => (
      <div>
        <Component />
        <Component />
        <Component />
      </div>
    );
    const ChildComponent = () => <div>The child</div>;
    
    const ParentComponent = () => (
      <>
        <h1>The Title</h1>
        <ChildComponent />
      </>
    );
    
    export { ChildComponent };
    export default ParentComponent;
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ image, link, title, id }, index) => (
          <QuickLinksItem
            image={image}
            index={index}
            length={items.length}
            link={link}
            title={title}
            key={id}
          />
        ))}
      </QuickLinksWrapper>
    );
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    const DataReport = ({ items }: { items: Array<string> }) => (
      <table>
        <tr className="bg-teal-dark text-lg text-white">
          <td className="cell-class">NAME</td>
          <td className="cell-class">HOURS</td>
        </tr>
        {items.map(({ hours, name }, index) => (
          <tr className="bg-transparent text-lg text-teal-dark" key={index}>
            <td className="cell-class">{name}</td>
            <td className="cell-class">{hours}</td>
          </tr>
        ))}
      </table>
    );
    const TableCell = ({ children }) => (
      <td className="cell-class">{children}</td>
    );
    
    
    const TableRow = ({ children, isHeader }) => {
    
    const class = isHeader ? 'bg-teal-dark text-white' : 'bg-transparent text-teal-dark'
      return <tr className={`${class} text-lg`}>{children}</td>
    };
    
    
    const DataReport = ({ items }: { items: Array<string> }) => (
      <table>
        <TableRow isHeader={true}>
          <TableCell>NAME</TableCell>
          <TableCell>HOURS</TableCell>
        </TableRow>
        {items.map(({ hours, name }, index) => (
          <TableRow key={index}>
            <TableCell>{name}</TableCell>
            <TableCell>{hours}</TableCell>
          </TableRow>
        ))}
      </table>
    );
    let obj: any = { x: 0 };
    // None of the following lines of code will throw compiler errors.
    // Using `any` disables all further type checking, and it is assumed
    // you know the environment better than TypeScript.obj.foo();obj();obj.bar = 100;obj = "hello";
    const n: number = obj;Try
    let myName: string = "Alice";Try
    // No type annotation needed -- 'myName' inferred as type 'string'let myName = "Alice";Try
    // Parameter type annotationfunction greet(name: string) {  console.log("Hello, " + name.toUpperCase() + "!!");}Try
    // Would be a runtime error if executed!greet(42);Argument of type 'number' is not assignable to parameter of type 'string'.Argument of type 'number' is not assignable to parameter of type 'string'.Try
    function getFavoriteNumber(): number {  return 26;}Try
    // No type annotations here, but TypeScript can spot the bug
    const names = ["Alice", "Bob", "Eve"];
    // Contextual typing for functionnames.forEach(function (s) {  console.log(s.toUppercase());Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?});
    // Contextual typing also applies to arrow functionsnames.forEach((s) => {  console.log(s.toUppercase());Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?});Try
    // The parameter's type annotation is an object typefunction printCoord(pt: { x: number; y: number }) {  console.log("The coordinate's x value is " + pt.x);  console.log("The coordinate's y value is " + pt.y);}printCoord({ x: 3, y: 7 });Try
    function printName(obj: { first: string; last?: string }) {
    // ...}
    // Both OKprintName({ first: "Bob" });printName({ first: "Alice", last: "Alisson" });Try
    function printName(obj: { first: string; last?: string }) {
    // Error - might crash if 'obj.last' wasn't provided!  console.log(obj.last.toUpperCase());Object is possibly 'undefined'.Object is possibly 'undefined'.  if (obj.last !== undefined) {
    // OK    console.log(obj.last.toUpperCase());  }
    // A safe alternative using modern JavaScript syntax:  console.log(obj.last?.toUpperCase());}Try
    function printId(id: number | string) {  console.log("Your ID is: " + id);}
    // OKprintId(101);
    // OKprintId("202");
    // ErrorprintId({ myID: 22342 });Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
      Type '{ myID: number; }' is not assignable to type 'number'.Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
      Type '{ myID: number; }' is not assignable to type 'number'.Try
    function printId(id: number | string) {  console.log(id.toUpperCase());Property 'toUpperCase' does not exist on type 'string | number'.
      Property 'toUpperCase' does not exist on type 'number'.Property 'toUpperCase' does not exist on type 'string | number'.
      Property 'toUpperCase' does not exist on type 'number'.}Try
    function printId(id: number | string) {  if (typeof id === "string") {
    // In this branch, id is of type 'string'    console.log(id.toUpperCase());  } else {
    // Here, id is of type 'number'    console.log(id);  }}Try
    function welcomePeople(x: string[] | string) {  if (Array.isArray(x)) {
    // Here: 'x' is 'string[]'    console.log("Hello, " + x.join(" and "));  } else {
    // Here: 'x' is 'string'    console.log("Welcome lone traveler " + x);  }}Try
    // Return type is inferred as number[] | stringfunction getFirstThree(x: number[] | string) {  return x.slice(0, 3);}Try
    type Point = {  x: number;  y: number;};
    // Exactly the same as the earlier examplefunction printCoord(pt: Point) {  console.log("The coordinate's x value is " + pt.x);  console.log("The coordinate's y value is " + pt.y);} printCoord({ x: 100, y: 100 });Try
    type ID = number | string;Try
    type UserInputSanitizedString = string; function sanitizeInput(str: string): UserInputSanitizedString {  return sanitize(str);}
    // Create a sanitized inputlet userInput = sanitizeInput(getInput());
    // Can still be re-assigned with a string thoughuserInput = "new input";Try
    interface Point {  x: number;  y: number;} function printCoord(pt: Point) {  console.log("The coordinate's x value is " + pt.x);  console.log("The coordinate's y value is " + pt.y);} printCoord({ x: 100, y: 100 });Try
    interface Animal {
    name: string
    }
    interface Bear extends Animal {
    honey: boolean
    }
    
    const bear = getBear()
    bear.name
    bear.honey
    type Animal = {
    name: string
    }
    type Bear = Animal & {
    honey: boolean
    }
    
    const bear = getBear();
    bear.name;
    bear.honey;
    interface Window {
    title: string
    }
    interface Window {
    ts: TypeScriptAPI
    }
    
    const src = '
    const a = "Hello World"';
    window.ts.transpileModule(src, {});
    type Window = {
    title: string
    }
    type Window = {
    ts: TypeScriptAPI
    }
    
    // Error: Duplicate identifier 'Window'.
    const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
    Try;
    const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
    Try;
    const x = "hello" as number;Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.Try
    const a = expr as any as T;
    Try;
    let changingString = "Hello World";changingString = "Olá Mundo";
    // Because `changingString` can represent any possible string, that
    // is how TypeScript describes it in the type systemchangingString;      let changingString: string
    const
    constantString = "Hello World";
    // Because `
    constantString` can only represent 1 possible string, it
    // has a literal type representation
    constantString;
    const
    constantString: "Hello World"Try
    let x: "hello" = "hello";
    // OKx = "hello";
    // ...x = "howdy";Type '"howdy"' is not assignable to type '"hello"'.Type '"howdy"' is not assignable to type '"hello"'.Try
    function printText(s: string, alignment: "left" | "right" | "center") {
    // ...}printText("Hello, world", "left");printText("G'day, mate", "centre");Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.Try
    function compare(a: string, b: string): -1 | 0 | 1 {  return a === b ? 0 : a > b ? 1 : -1;}Try
    interface Options {  width: number;}function configure(x: Options | "auto") {
    // ...}configure({ width: 100 });configure("auto");configure("automatic");Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.Try
    const obj = { counter: 0 };
    if (someCondition) {
      obj.counter = 1;
    }
    Try;
    const req = { url: "https:
    //example.com", method: "GET" };handleRequest(req.url, req.method);Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.Try
        Change 1 means "I intend for `req.method` to always have the _literal type_ `"GET"`", preventing the possible assignment of `"GUESS"` to that field after. Change 2 means "I know for other reasons that `req.method` has the value `"GET"`".
    
    2.  You can use `as const` to convert the entire object to be type literals:
    
            ```
    
    const req = { url: "https:
    //example.com", method: "GET" } as
    const;handleRequest(req.url, req.method);Try
    function doSomething(x: string | null) {  if (x === null) {
    // do nothing  } else {    console.log("Hello, " + x.toUpperCase());  }}Try
    function liveDangerously(x?: number | null) {
    // No error  console.log(x!.toFixed());}Try
    // Creating a bigint via the BigInt function
    const oneHundred: bigint = BigInt(100);
    // Creating a BigInt via the literal syntax
    const anotherHundred: bigint = 100n;
    Try;
    const firstName = Symbol("name");
    const secondName = Symbol("name"); if (firstName === secondName) {This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
    // Can't ever happen}Try
    var React = require("react");
    var MyComponent = require("MyComponent");
    
    function render() {
      return <MyComponent foo="bar" />;
    }
    var React = require("react");
    var MyComponent = React.createFactory(require("MyComponent"));
    
    function render() {
      return MyComponent({ foo: "bar" });
    }
    var React = require("react");
    
    function render(MyComponent) {
      return React.createElement(MyComponent, { foo: "bar" });
    }
    Button.propTypes = {
      highlighted: PropTypes.bool,
    };
    var apiShape = PropTypes.shape({
      body: PropTypes.object,
      statusCode: PropTypes.number.isRequired,
    }).isRequired;
    
    // Not supported!
    var error = apiShape(json, "response");
    Button.propTypes = {
      highlighted: ThirdPartyPropTypes.deprecated(
        PropTypes.bool,
        "Use `active` prop instead"
      ),
    };
    export default function deprecated(propType, explanation) {
      return function validate(props, propName, componentName) {
        if (props[propName] != null) {
          const message = `"${propName}" property of "${componentName}" has been deprecated.\n${explanation}`;
          if (!warned[message]) {
            warning(false, message);
            warned[message] = true;
          }
        }
    
        return propType(props, propName, componentName);
      };
    }
    export default function deprecated(propType, explanation) {
      return function validate(props, propName, componentName, ...rest) {
        // Note ...rest here
        if (props[propName] != null) {
          const message = `"${propName}" property of "${componentName}" has been deprecated.\n${explanation}`;
          if (!warned[message]) {
            warning(false, message);
            warned[message] = true;
          }
        }
    
        return propType(props, propName, componentName, ...rest); // and here
      };
    }
    <div id="percent-loaded" role="progressbar" aria-valuenow="75"
         aria-valuemin="0" aria-valuemax="100">
    </div>
    // Find the progress bar <div> in the DOM.
    var progressBar = document.getElementById("percent-loaded");
    
    // Set its ARIA roles and states,
    // so that assistive technologies know what kind of widget it is.
    progressBar.setAttribute("role", "progressbar");
    progressBar.setAttribute("aria-valuemin", 0);
    progressBar.setAttribute("aria-valuemax", 100);
    
    // Create a function that can be called at any time to update
    // the value of the progress bar.
    function updateProgress(percentComplete) {
      progressBar.setAttribute("aria-valuenow", percentComplete);
    }
    <progress id="percent-loaded" value="75" max="100">75 %</progress>
    looks like this: (note - only a few fields are shown as an example here)
    and return this array back to
    <SingleStepForm />
  • This function uses a Typescript overload to determine the correct return type depending on which multi boolean is passed in

  • inputMap is an object with field names as a key and their dataMap type as the value. These are basically fields that we've determined to be non-input type fields, such as select, checkbox, heading, etc... or fields that are hidden or unimportant

    validations: Will either be null or an object that contains if it should show on the confirmation screen and the validationPattern if any

    inside <FieldWrapper /> it will nest the dynamic <Component /> that was returned and will pass in some props, those are:

    • data: The data object returned from createForm()

    • errors: This is an object created by react-hook-form that contains key/value pairs of the field name and the error message

    • name: The name of the field from the data object. react-hook-form requires the name prop to exist at this point

    • props: The props object returned from createForm()

    • register: A function from react-hook-form that will eventually get passed all the way down to the input to be called on that input's ref. It has:

      • pattern: A regex pattern matching validation

      • required: A boolean or error message

    for example. Each file will have a Form[Input] component with its related Input component. This Input component will then be used in tests and storybook stories.
    Partial<Type>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs a type with all properties of Type set to optional. This utility will return a type that represents all subsets of a given type.

    Example

    Required<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#improved-control-over-mapped-type-modifiers)

    constructs a type consisting of all properties of Type set to required. The opposite of [Partial](https: //www.typescriptlang.org/docs/handbook/utility-types.html#partialtype).

    Example

    Readonly<Type>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.

    Example

    This utility is useful for representing assignment expressions that will fail at runtime (i.e. when attempting to reassign properties of a [frozen object](https: //developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)).

    Object.freeze

    Record<Keys, Type>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.

    Example

    Pick<Type, Keys>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs a type by picking the set of properties Keys (string literal or union of string literals) from Type.

    Example

    Omit<Type, Keys>

    Released: [3.5](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type)

    constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals).

    Example

    Exclude<UnionType, ExcludedMembers>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type by excluding from UnionType all union members that are assignable to ExcludedMembers.

    Example

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type by extracting from Type all union members that are assignable to Union.

    Example

    NonNullable<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type by excluding null and undefined from Type.

    Example

    Parameters<Type>

    Released: [3.1](https: //github.com/microsoft/TypeScript/pull/26243)

    constructs a tuple type from the types used in the parameters of a function type Type.

    Example

    `

    constructorParameters`

    Released: [3.1](https: //github.com/microsoft/TypeScript/pull/26243)

    constructs a tuple or array type from the types of a constructor function type. It produces a tuple type with all the parameter types (or the type never if Type is not a function).

    Example

    ReturnType<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type consisting of the return type of function Type.

    Example

    InstanceType<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type consisting of the instance type of a constructor function in Type.

    Example

    ThisParameterType<Type>

    Released: [3.3](https: //github.com/microsoft/TypeScript/pull/28920)

    Extracts the type of the [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this-parameters) parameter for a function type, or [unknown](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type) if the function type has no this parameter.

    Example

    OmitThisParameter<Type>

    Released: [3.3](https: //github.com/microsoft/TypeScript/pull/28920)

    Removes the [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this-parameters) parameter from Type. If Type has no explicitly declared this parameter, the result is simply Type. Otherwise, a new function type with no this parameter is created from Type. Generics are erased and only the last overload signature is propagated into the new function type.

    Example

    ThisType<Type>

    Released: [2.3](https: //github.com/microsoft/TypeScript/pull/14141)

    This utility does not return a transformed type. Instead, it serves as a marker for a contextual [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this) type. Note that the [noImplicitThis](https: //www.typescriptlang.org/tsconfig#noImplicitThis) flag must be enabled to use this utility.

    Example

    In the example above, the methods object in the argument to makeObject has a contextual type that includes ThisType<D & M> and therefore the type of [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this) in methods within the methods object is { x: number, y: number } & { moveBy(dx: number, dy: number): number }. Notice how the type of the methods property simultaneously is an inference target and a source for the this type in methods.

    The ThisType<T> marker interface is simply an empty interface declared in lib.d.ts. Beyond being recognized in the contextual type of an object literal, the interface acts like any empty interface.

    Intrinsic String Manipulation Types

    Uppercase<StringType>

    Lowercase<StringType>

    Capitalize<StringType>

    Uncapitalize<StringType>

    To help with string manipulation around template string literals, TypeScript includes a set of types which can be used in string manipulation within the type system. You can find those in the [Template Literal Types](https: //www.typescriptlang.org/docs/handbook/2/template-literal-types.html#uppercasestringtype) documentation.

    enum GlobalConfigKey {
      ApigeeServiceHost,
      ApigeeServiceHostCC,
      ApiHost,
      AppInsightsKey,
      SITECORE_API_KEY,
      GoogleTagManagerId,
    }
    
    type GlobalConfig = {
      [key in keyof typeof GlobalConfigKey]?: string;
    };
    
    interface Window {
      globalConfig: GlobalConfig;
      angular?: any;
    }
    
    declare let globalConfig: GlobalConfig;
    
    declare namespace JSS {
      import("@sitecore-jss/sitecore-jss-react");
      import { Text } from "@sitecore-jss/sitecore-jss-react";
    
      type Field<
        Component extends (...args: any) => any,
        Key extends Parameters<Component>[0] = "field"
      > = Pick<Parameters<Component>[0], Key>[Key];
    
      export type LinkField = {
        value: {
          [K in
            | "href"
            | "id"
            | "querystring"
            | "text"
            | "title"
            | "target"
            | "class"
            | "url"
            | "linktype"]?: string;
        };
        editable?: string;
        editableFirstPart?: string;
        editableLastPart?: string;
      };
    
      type ImageField = {
        value?: {
          [K in "alt" | "height" | "src" | 
        };
        editable?: string;
      };
    
      type TextField = Field<typeof Text>;
    
      export type { LinkField, ImageField, TextField };
    }
    tsinterface Todo {  title: string;  description: string;}function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {  return { ...todo, ...fieldsToUpdate };}
    const todo1 = {  title: "organize desk",  description: "clear clutter",};
    const todo2 = updateTodo(todo1, {  description: "throw out trash",});Try
    tsinterface Props {  a?: number;  b?: string;}
    const obj: Props = { a: 5 };
    const obj2: Required<Props> = { a: 5 };Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.2741Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.Try
    tsinterface Todo {  title: string;}
    const todo: Readonly<Todo> = {  title: "Delete inactive users",};todo.title = "Hello";Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.Try
    tsfunction freeze<Type>(obj: Type): Readonly<Type>;
    tsinterface CatInfo {  age: number;  breed: string;}type CatName = "miffy" | "boris" | "mordred";
    const cats: Record<CatName, CatInfo> = {  miffy: { age: 10, breed: "Persian" },  boris: { age: 5, breed: "Maine Coon" },  mordred: { age: 16, breed: "British Shorthair" },};cats.boris;
    const cats: Record<CatName, CatInfo>Try
    tsinterface Todo {  title: string;  description: string;  completed: boolean;}type TodoPreview = Pick<Todo, "title" | "completed">;
    const todo: TodoPreview = {  title: "Clean room",  completed: false,};todo;
    const todo: TodoPreviewTry
    tsinterface Todo {  title: string;  description: string;  completed: boolean;  createdAt: number;}type TodoPreview = Omit<Todo, "description">;
    const todo: TodoPreview = {  title: "Clean room",  completed: false,  createdAt: 1615544252770,};todo;
    const todo: TodoPreviewtype TodoInfo = Omit<Todo, "completed" | "createdAt">;
    const todoInfo: TodoInfo = {  title: "Pick up kids",  description: "Kindergarten closes at 5pm",};todoInfo;
    const todoInfo: TodoInfoTry
    tstype T0 = Exclude<"a" | "b" | "c", "a">;     type T0 = "b" | "c"type T1 = Exclude<"a" | "b" | "c", "a" | "b">;     type T1 = "c"type T2 = Exclude<string | number | (() => void), Function>;     type T2 = string | numberTry
    tstype T0 = Extract<"a" | "b" | "c", "a" | "f">;     type T0 = "a"type T1 = Extract<string | number | (() => void), Function>;     type T1 = () => voidTry
    tstype T0 = NonNullable<string | number | undefined>;     type T0 = string | numbertype T1 = NonNullable<string[] | null | undefined>;     type T1 = string[]Try
    tsdeclare function f1(arg: { a: number; b: string }): void;type T0 = Parameters<() => string>;     type T0 = []type T1 = Parameters<(s: string) => void>;     type T1 = [s: string]type T2 = Parameters<<T>(arg: T) => T>;     type T2 = [arg: unknown]type T3 = Parameters<typeof f1>;     type T3 = [arg: {
        a: number;
        b: string;
    }]type T4 = Parameters<any>;     type T4 = unknown[]type T5 = Parameters<never>;     type T5 = nevertype T6 = Parameters<string>;Type 'string' does not satisfy the
    constraint '(...args: any) => any'.2344Type 'string' does not satisfy the
    constraint '(...args: any) => any'.     type T6 = nevertype T7 = Parameters<Function>;Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.2344Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.     type T7 = neverTry
    tstype T0 =
    constructorParameters<Error
    constructor>;     type T0 = [message?: string]type T1 =
    constructorParameters<Function
    constructor>;     type T1 = string[]type T2 =
    constructorParameters<RegExp
    constructor>;     type T2 = [pattern: string | RegExp, flags?: string]type T3 =
    constructorParameters<any>;     type T3 = unknown[]type T4 =
    constructorParameters<Function>;Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
      Type 'Function' provides no match for the signature 'new (...args: any): any'.2344Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
    
    tsdeclare function f1(): { a: number; b: string };type T0 = ReturnType<() => string>;     type T0 = stringtype T1 = ReturnType<(s: string) => void>;     type T1 = voidtype T2 = ReturnType<<T>() => T>;     type T2 = unknowntype T3 = ReturnType<<T extends U, U extends number[]>() => T>;     type T3 = number[]type T4 = ReturnType<typeof f1>;     type T4 = {
        a: number;
        b: string;
    }type T5 = ReturnType<any>;     type T5 = anytype T6 = ReturnType<never>;     type T6 = nevertype T7 = ReturnType<string>;Type 'string' does not satisfy the
    constraint '(...args: any) => any'.2344Type 'string' does not satisfy the
    constraint '(...args: any) => any'.     type T7 = anytype T8 = ReturnType<Function>;Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.2344Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.     type T8 = anyTry
    tsclass C {  x = 0;  y = 0;}type T0 = InstanceType<typeof C>;     type T0 = Ctype T1 = InstanceType<any>;     type T1 = anytype T2 = InstanceType<never>;     type T2 = nevertype T3 = InstanceType<string>;Type 'string' does not satisfy the
    constraint 'abstract new (...args: any) => any'.2344Type 'string' does not satisfy the
    constraint 'abstract new (...args: any) => any'.     type T3 = anytype T4 = InstanceType<Function>;Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
      Type 'Function' provides no match for the signature 'new (...args: any): any'.2344Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
      Type 'Function' provides no match for the signature 'new (...args: any): any'.     type T4 = anyTry
    tsfunction toHex(this: Number) {  return this.toString(16);}function numberToString(n: ThisParameterType<typeof toHex>) {  return toHex.apply(n);}Try
    tsfunction toHex(this: Number) {  return this.toString(16);}
    const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);console.log(fiveToHex());Try
    tstype ObjectDescriptor<D, M> = {  data?: D;  methods?: M & ThisType<D & M>;
    // Type of 'this' in methods is D & M};function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {  let data: object = desc.data || {};  let methods: object = desc.methods || {};  return { ...data, ...methods } as D & M;}let obj = makeObject({  data: { x: 0, y: 0 },  methods: {    moveBy(dx: number, dy: number) {      this.x += dx;
    // Strongly typed this      this.y += dy;
    // Strongly typed this    },  },});obj.x = 10;obj.y = 20;obj.moveBy(5, 5);Try
    element has no
    autocomplete
    attribute, then browsers use the
    autocomplete
    attribute of the element's form owner, which is either the
    element that the element is a descendant of, or the <form> whose id is specified by the
    attribute of the element.

    For more information, see the autocompletearrow-up-right attribute in <form>arrow-up-right.

    Note: In order to provide autocompletion, user-agents might require <input>/<select>/<textarea> elements to:

    1. Have a name and/or id attribute

    2. Be descendants of a <form> element

    3. The form to have a button

    hashtag
    Valuesarrow-up-right

    "off"

    The browser is not permitted to automatically enter or select a value for this field. It is possible that the document or application provides its own autocomplete feature, or that security concerns require that the field's value not be automatically entered.

    Note: In most modern browsers, setting autocomplete to "off" will not prevent a password manager from asking the user if they would like to save username and password information, or from automatically filling in those values in a site's login form. See the autocomplete attribute and login fieldsarrow-up-right.

    "on"

    The browser is allowed to automatically complete the input. No guidance is provided as to the type of data expected in the field, so the browser may use its own judgement.

    "name"

    The field expects the value to be a person's full name. Using "name" rather than breaking the name down into its components is generally preferred because it avoids dealing with the wide diversity of human names and how they are structured; however, you can use the following autocomplete values if you do need to break the name down into its components:

    "honorific-prefix"

    The prefix or title, such as "Mrs.", "Mr.", "Miss", "Ms.", "Dr.", or "Mlle.".

    "given-name"

    The given (or "first") name.

    "additional-name"

    The middle name.

    "family-name"

    The family (or "last") name.

    "honorific-suffix"

    The suffix, such as "Jr.", "B.Sc.", "PhD.", "MBASW", or "IV".

    "nickname"

    A nickname or handle.

    "email"

    An email address.

    "username"

    A username or account name.

    "new-password"

    A new password. When creating a new account or changing passwords, this should be used for an "Enter your new password" or "Confirm new password" field, as opposed to a general "Enter your current password" field that might be present. This may be used by the browser both to avoid accidentally filling in an existing password and to offer assistance in creating a secure password (see also Preventing autofilling with autocomplete="new-password"arrow-up-right).

    "current-password"

    The user's current password.

    "one-time-code"

    A one-time code used for verifying user identity.

    "organization-title"

    A job title, or the title a person has within an organization, such as "Senior Technical Writer", "President", or "Assistant Troop Leader".

    "organization"

    A company or organization name, such as "Acme Widget Company" or "Girl Scouts of America".

    "street-address"

    A street address. This can be multiple lines of text, and should fully identify the location of the address within its second administrative level (typically a city or town), but should not include the city name, ZIP or postal code, or country name.

    "address-line1", "address-line2", "address-line3"

    Each individual line of the street address. These should only be present if the "street-address" is not present.

    "address-level4"

    The finest-grained administrative levelarrow-up-right, in addresses which have four levels.

    "address-level3"

    The third administrative levelarrow-up-right, in addresses with at least three administrative levels.

    "address-level2"

    The second administrative levelarrow-up-right, in addresses with at least two of them. In countries with two administrative levels, this would typically be the city, town, village, or other locality in which the address is located.

    "address-level1"

    The first administrative levelarrow-up-right in the address. This is typically the province in which the address is located. In the United States, this would be the state. In Switzerland, the canton. In the United Kingdom, the post town.

    "country"

    A country or territory code.

    "country-name"

    A country or territory name.

    "postal-code"

    A postal code (in the United States, this is the ZIP code).

    "cc-name"

    The full name as printed on or associated with a payment instrument such as a credit card. Using a full name field is preferred, typically, over breaking the name into pieces.

    "cc-given-name"

    A given (first) name as given on a payment instrument like a credit card.

    "cc-additional-name"

    A middle name as given on a payment instrument or credit card.

    "cc-family-name"

    A family name, as given on a credit card.

    "cc-number"

    A credit card number or other number identifying a payment method, such as an account number.

    "cc-exp"

    A payment method expiration date, typically in the form "MM/YY" or "MM/YYYY".

    "cc-exp-month"

    The month in which the payment method expires.

    "cc-exp-year"

    The year in which the payment method expires.

    "cc-csc"

    The security code for the payment instrument; on credit cards, this is the 3-digit verification number on the back of the card.

    "cc-type"

    The type of payment instrument (such as "Visa" or "Master Card").

    "transaction-currency"

    The currency in which the transaction is to take place.

    "transaction-amount"

    The amount, given in the currency specified by "transaction-currency", of the transaction, for a payment form.

    "language"

    A preferred language, given as a valid BCP 47 language tagarrow-up-right.

    "bday"

    A birth date, as a full date.

    "bday-day"

    The day of the month of a birth date.

    "bday-month"

    The month of the year of a birth date.

    "bday-year"

    The year of a birth date.

    "sex"

    A gender identity (such as "Female", "Fa'afafine", "Male"), as freeform text without newlines.

    "tel"

    A full telephone number, including the country code. If you need to break the phone number up into its components, you can use these values for those fields:

    "tel-country-code"

    The country code, such as "1" for the United States, Canada, and other areas in North America and parts of the Caribbean.

    "tel-national"

    The entire phone number without the country code component, including a country-internal prefix. For the phone number "1-855-555-6502", this field's value would be "855-555-6502".

    "tel-area-code"

    The area code, with any country-internal prefix applied if appropriate.

    "tel-local"

    The phone number without the country or area code. This can be split further into two parts, for phone numbers which have an exchange number and then a number within the exchange. For the phone number "555-6502", use "tel-local-prefix" for "555" and "tel-local-suffix" for "6502".

    "tel-extension"

    A telephone extension code within the phone number, such as a room or suite number in a hotel or an office extension in a company.

    "impp"

    A URL for an instant messaging protocol endpoint, such as "xmpp:username@example.net".

    "url"

    A URL, such as a home page or company web site address as appropriate given the context of the other fields in the form.

    "photo"

    The URL of an image representing the person, company, or contact information given in the other fields in the form.

    See the WHATWG Standardarrow-up-right for more detailed information.

    Note: The autocomplete attribute also controls whether Firefox will — unlike other browsers — persist the dynamic disabled state and (if applicable) dynamic checkednessarrow-up-right of an <input> element, <textarea> element, or entire <form> across page loads. The persistence feature is enabled by default. Setting the value of the autocomplete attribute to off disables this feature. This works even when the autocomplete attribute would normally not apply by virtue of its type. See bug 654072arrow-up-right.

    hashtag
    Examplesarrow-up-right

    hashtag
    Administrative levels in addressesarrow-up-right

    The four administrative level fields (address-level1 through address-level4) describe the address in terms of increasing levels of precision within the country in which the address is located. Each country has its own system of administrative levels, and may arrange the levels in different orders when addresses are written.

    address-level1 always represents the broadest administrative division; it is the least-specific portion of the address short of the country name.

    hashtag
    Form layout flexibilityarrow-up-right

    Given that different countries write their address in different ways, with each field in different places within the address, and even different sets and numbers of fields entirely, it can be helpful if, when possible, your site is able to switch to the layout expected by your users when presenting an address entry form, given the country the address is located within.

    hashtag
    Variationsarrow-up-right

    The way each administrative level is used will vary from country to country. Below are some examples; this is not meant to be an exhaustive list.

    United States

    A typical home address within the United States looks like this:

    432 Anywhere St Exampleville CA 95555

    In the United States, the least-specific portion of the address is the state, in this case "CA" (the official US Postal Service shorthand for "California"). Thus address-level1 is the state, or "CA" in this case.

    The second-least specific portion of the address is the city or town name, so address-level2 is "Exampleville" in this example address.

    United States addresses do not use levels 3 and up.

    United Kingdom

    Address input forms in the UK should contain one address level and one, two or three address lines, depending on the address. A complete address would look like so:

    103 Frogmarch Street Upper-Wapping Winchelsea TN99 8ZZ

    The address levels are:

    • address-level1: The post town — "Winchelsea" in this case.

    • address-line2: The locality — "Upper-Wapping" in this case.

    • address-line1: The house/street particulars — "103 Frogmarch Street"

    The postcode is separate. Note that you can actually use just the postcode and address-line1 to successfully deliver mail in the UK, so they should be the only mandatory items, but usually people tend to provide more details.

    China

    China can use as many as three administrative levels: the province, the city, and the district.

    The 6 digit postal code is not always needed but when supplied it is placed separately with a label for clarity. For example:

    北京市东城区建国门北大街 8 号华润大厦 17 层 1708 单元 邮编:100005

    Japan

    An address in Japan is typically written in one line, in an order from the least-specific to more-specific portions (in reverse order to the United States). There are two or three administrative levels in an address. Additional line can be used to show building names and room numbers. The postal code is separate. For example:

    〒 381-0000 長野県長野市某町 123

    "〒" and following seven digits shows the postal code.

    address-level1 is used for prefectures or the Tokyo Metropolis; "長野県" (Nagano Prefecture) is in this case. address-level2 is typically used for cities, counties, towns and villages; "長野市" (Nagano City) in this case. "某町 123" is address-line1 which consists of an area name and a lot number.

    hashtag
    Specificationsarrow-up-right

    Specification

    [HTML Standard

    # attr-fe-autocomplete](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete)

    hashtag
    Browser compatibilityarrow-up-right

    Report problems with this compatibility data on GitHubarrow-up-right

    hashtag
    Legend

    Full support

    Full support

    No support

    No support

    Compatibility unknown

    Compatibility unknown

    See implementation notes.

    The compatibility table on this page is generated from structured data. If you'd like to contribute to the data, please check out https://github.com/mdn/browser-compat-dataarrow-up-right and send us a pull request.

    hashtag
    See alsoarrow-up-right

    • The <input>arrow-up-right element.

    • The <select>arrow-up-right element.

    • The <textarea>arrow-up-right element.

    • The element.

    • All .

    user agentarrow-up-right
    <input>arrow-up-right
    <textarea>arrow-up-right
    <select>arrow-up-right
    <form>arrow-up-right
    <input>arrow-up-right
    <select>arrow-up-right
    <textarea>arrow-up-right
    <form>arrow-up-right
    formarrow-up-right

    hashtag
    Intent

    The intent of this Success Criterion is to ensure that when users navigate sequentially through content, they encounter information in an order that is consistent with the meaning of the content and can be operated from the keyboard. This reduces confusion by letting users form a consistent mental model of the content. There may be different orders that reflect logical relationships in the content. For example, moving through components in a table one row at a time or one column at a time both reflect the logical relationships in the content. Either order may satisfy this Success Criterion.

    The way that sequential navigation order is determined in Web content is defined by the technology of the content. For example, simple HTML defines sequential navigation via the notion of tabbing order. Dynamic HTML may modify the navigation sequence using scripting along with the addition of a tabindex attribute to allow focus to additional elements. If no scripting or tabindex attributes are used, the navigation order is the order that components appear in the content stream. (See HTML 4.01 Specification, section 17.11, "Giving focus to an element").

    An example of keyboard navigation that is not the sequential navigation addressed by this Success Criterion is using arrow key navigation to traverse a tree component. The user can use the up and down arrow keys to move from tree node to tree node. Pressing the right arrow key may expand a node, then using the down arrow key, will move into the newly expanded nodes. This navigation sequence follows the expected sequence for a tree control - as additional items get expanded or collapsed, they are added or removed from the navigation sequence.

    The focus order may not be identical to the programmatically determined reading order (see Success Criterion 1.3.2) as long as the user can still understand and operate the Web page. Since there may be several possible logical reading orders for the content, the focus order may match any of them. However, when the order of a particular presentation differs from the programmatically determined reading order, users of one of these presentations may find it difficult to understand or operate the Web page. Authors should carefully consider all these users as they design their Web pages.

    For example, a screen reader user interacts with the programmatically determined reading order, while a sighted keyboard user interacts with the visual presentation of the Web page. Care should be taken so that the focus order makes sense to both of these sets of users and does not appear to either of them to jump around randomly.

    For clarity:

    1. Focusable components need to receive focus in an order that preserves meaning and operability only when navigation sequences affect meaning and operability.

    2. In those cases where it is required, there may be more than one order that will preserve meaning and operability.

    3. If there is more than one order that preserves meaning and operability, only one of them needs to be provided.

    hashtag
    Benefits

    These techniques benefit keyboard users who navigate documents sequentially and expect the focus order to be consistent with the sequential reading order.

    • People with mobility impairments who must rely on keyboard access for operating a page benefit from a logical, usable focus order.

    • People with disabilities that make reading difficult can become disoriented when tabbing takes focus someplace unexpected. They benefit from a logical focus order.

    • People with visual impairments can become disoriented when tabbing takes focus someplace unexpected or when they cannot easily find the content surrounding an interactive element.

    • Only a small portion of the page may be visible to an individual using a screen magnifier at a high level of magnification. Such a user may interpret a field in the wrong context if the focus order is not logical.

    hashtag
    Examples

    1. On a web page that contains a tree of interactive controls, the user can use the up and down arrow keys to move from tree node to tree node. Pressing the right arrow key expands a node, then using the down arrow key moves into the newly expanded nodes.

    2. A Web page implements modeless dialogs via scripting. When the trigger button is activated, a dialog opens. The interactive elements in the dialog are inserted in the focus order immediately after the button. When the dialog is open, the focus order goes from the button to the elements of the dialog, then to the interactive element following the button. When the dialog is closed, the focus order goes from the button to the following element.

    3. A Web page implements modal dialogs via scripting. When the trigger button is activated, a dialog opens and focus is set to the first interactive element in the dialog. As long as the dialog is open, focus is limited to the elements of the dialog. When the dialog is dismissed, focus returns to the button or the element following the button.

    4. An HTML Web page is created with the left hand navigation occurring in the HTML after the main body content, and styled with CSS to appear on the left hand side of the page. This is done to allow focus to move to the main body content first without requiring tabIndex attributes or JavaScript.

      Note

      While this example passes the Success Criterion, it is not necessarily true that all CSS positioning would. More complex positioning examples may or may not preserve meaning and operability

    5. The following example fails to meet the Success Criterion:

      A company's Web site includes a form that collects marketing data and allows users to subscribe to several newsletters published by the company. The section of the form for collecting marketing data includes fields such as name, street address, city, state or province, and postal code. Another section of the form includes several checkboxes so that users can indicate newsletters they want to receive. However, the tab order for the form skips between fields in different sections of the form, so that focus moves from the name field to a checkbox, then to the street address, then to another checkbox.

    hashtag
    Techniques

    Each numbered item in this section represents a technique or combination of techniques that the WCAG Working Group deems sufficient for meeting this Success Criterion. However, it is not necessary to use these particular techniques. For information on using other techniques, see Understanding Techniques for WCAG Success Criteriaarrow-up-right, particularly the "Other Techniques" section.

    hashtag
    Sufficient Techniques

    1. G59: Placing the interactive elements in an order that follows sequences and relationships within the contentarrow-up-right

    2. Giving focus to elements in an order that follows sequences and relationships within the content using one of the following techniques:

      • C27: Making the DOM order match the visual orderarrow-up-right

    3. Changing a Web page dynamically using one of the following techniques:

    hashtag
    Failures

    The following are common mistakes that are considered failures of this Success Criterion by the WCAG Working Group.

    • F44: Failure of Success Criterion 2.4.3 due to using tabindex to create a tab order that does not preserve meaning and operabilityarrow-up-right

    • F85: Failure of Success Criterion 2.4.3 due to using dialogs or menus that are not adjacent to their trigger control in the sequential navigation orderarrow-up-right

    hashtag
    Key Terms

    assistive technology

    hardware and/or software that acts as a user agentarrow-up-right, or along with a mainstream user agent, to provide functionality to meet the requirements of users with disabilities that go beyond those offered by mainstream user agents

    Note

    functionality provided by assistive technology includes alternative presentations (e.g., as synthesized speech or magnified content), alternative input methods (e.g., voice), additional navigation or orientation mechanisms, and content transformations (e.g., to make tables more accessible).

    Note

    Assistive technologies often communicate data and messages with mainstream user agents by using and monitoring APIs.

    Note

    The distinction between mainstream user agents and assistive technologies is not absolute. Many mainstream user agents provide some features to assist individuals with disabilities. The basic difference is that mainstream user agents target broad and diverse audiences that usually include people with and without disabilities. Assistive technologies target narrowly defined populations of users with specific disabilities. The assistance provided by an assistive technology is more specific and appropriate to the needs of its target users. The mainstream user agent may provide important functionality to assistive technologies like retrieving Web content from program objects or parsing markup into identifiable bundles.

    Assistive technologies that are important in the context of this document include the following:

    • screen magnifiers, and other visual reading assistants, which are used by people with visual, perceptual and physical print disabilities to change text font, size, spacing, color, synchronization with speech, etc. in order to improve the visual readability of rendered text and images;

    • screen readers, which are used by people who are blind to read textual information through synthesized speech or braille;

    • text-to-speech software, which is used by some people with cognitive, language, and learning disabilities to convert text into synthetic speech;

    • speech recognition software, which may be used by people who have some physical disabilities;

    • alternative keyboards, which are used by people with certain physical disabilities to simulate the keyboard (including alternate keyboards that use head pointers, single switches, sip/puff and other special input devices.);

    • alternative pointing devices, which are used by people with certain physical disabilities to simulate mouse pointing and button activations.

    keyboard interface

    interface used by software to obtain keystroke input

    Note

    A keyboard interface allows users to provide keystroke input to programs even if the native technology does not contain a keyboard.

    A touchscreen PDA has a keyboard interface built into its operating system as well as a connector for external keyboards. Applications on the PDA can use the interface to obtain keyboard input either from an external keyboard or from other applications that provide simulated keyboard output, such as handwriting interpreters or speech-to-text applications with "keyboard emulation" functionality.

    Note

    Operation of the application (or parts of the application) through a keyboard-operated mouse emulator, such as MouseKeys, does not qualify as operation through a keyboard interface because operation of the program is through its pointing device interface, not through its keyboard interface.

    navigated sequentially

    navigated in the order defined for advancing focus (from one element to the next) using a keyboard interfacearrow-up-right

    user agent

    any software that retrieves and presents Web content for users

    Web browsers, media players, plug-ins, and other programs — including assistive technologiesarrow-up-right — that help in retrieving, rendering, and interacting with Web content.

    web page

    a non-embedded resource obtained from a single URI using HTTP plus any other resources that are used in the rendering or intended to be rendered together with it by a user agentarrow-up-right

    Note

    Although any "other resources" would be rendered together with the primary resource, they would not necessarily be rendered simultaneously with each other.

    Note

    For the purposes of conformance with these guidelines, a resource must be "non-embedded" within the scope of conformance to be considered a Web page.

    A Web resource including all embedded images and media.

    A Web mail program built using Asynchronous JavaScript and XML (AJAX). The program lives entirely at http://example.com/mail, but includes an inbox, a contacts area and a calendar. Links or buttons are provided that cause the inbox, contacts, or calendar to display, but do not change the URI of the page as a whole.

    A customizable portal site, where users can choose content to display from a set of different content modules.

    When you enter "http://shopping.example.com/" in your browser, you enter a movie-like interactive shopping environment where you visually move around in a store dragging products off of the shelves around you and into a visual shopping cart in front of you. Clicking on a product causes it to be demonstrated with a specification sheet floating alongside. This might be a single-page Web site or just one page within a Web site.

    Page Contents

    hashtag
    Accessibility in Context

    The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect.

    The Web is fundamentally designed to work for all people, whatever their hardware, software, language, location, or ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability.

    Thus the impact of disability is radically changed on the Web because the Web removes barriers to communication and interaction that many people face in the physical world. However, when websites, applications, technologies, or tools are badly designed, they can create barriers that exclude people from using the Web.

    Accessibility is essential for developers and organizations that want to create high-quality websites and web tools, and not exclude people from using their products and services.

    hashtag
    What is Web Accessibility

    Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can:

    • perceive, understand, navigate, and interact with the Web

    • contribute to the Web

    Web accessibility encompasses all disabilities that affect access to the Web, including:

    • auditory

    • cognitive

    • neurological

    • physical

    • speech

    • visual

    Web accessibility also benefits people without disabilities, for example:

    • people using mobile phones, smart watches, smart TVs, and other devices with small screens, different input modes, etc.

    • older people with changing abilities due to ageing

    • people with “temporary disabilities” such as a broken arm or lost glasses

    • people with “situational limitations” such as in bright sunlight or in an environment where they cannot listen to audio

    • people using a slow Internet connection, or who have limited or expensive bandwidth

    For a 7-minute video with examples of how accessibility is essential for people with disabilities and useful for everyone in a variety of situations, see: Web Accessibility Perspectives Video (YouTube)arrow-up-right

    hashtag
    Accessibility is Important for Individuals, Businesses, Society

    The Web is an increasingly important resource in many aspects of life: education, employment, government, commerce, health care, recreation, and more. It is essential that the Web be accessible in order to provide equal access and equal opportunity to people with diverse abilities. Access to information and communications technologies, including the Web, is defined as a basic human right in the United Nations Convention on the Rights of Persons with Disabilities (UN CRPDarrow-up-right).

    The Web offers the possibility of unprecedented access to information and interaction for many people with disabilities. That is, the accessibility barriers to print, audio, and visual media can be much more easily overcome through web technologies.

    Accessibility supports social inclusion for people with disabilities as well as others, such as:

    • older people

    • people in rural areas

    • people in developing countries

    There is also a strong business case for accessibility. As shown in the previous section, accessible design improves overall user experience and satisfaction, especially in a variety of situations, across different devices, and for older users. Accessibility can enhance your brand, drive innovation, and extend your market reach.

    Web accessibility is required by law in many situations.

    hashtag
    Making the Web Accessible

    Web accessibility depends on several components working together, including web technologies, web browsers and other "user agents", authoring tools, and websites.

    The W3C Web Accessibility Initiative (WAIarrow-up-right) develops technical specifications, guidelines, techniques, and supporting resources that describe accessibility solutions. These are considered international standards for web accessibility; for example, WCAG 2.0 is also an ISO standard: ISO/IEC 40500.

    hashtag
    Making Your Website Accessible

    Many aspects of accessibility are fairly easy to understand and implement. Some accessibility solutions are more complex and take more knowledge to implement.

    It is most efficient and effective to incorporate accessibility from the very beginning of projects, so you don’t need go back and to re-do work.

    hashtag
    Evaluating Accessibility

    When developing or redesigning a website, evaluate accessibility early and throughout the development process to identify accessibility problems early, when it is easier to address them. Simple steps, such as changing settings in a browser, can help you evaluate some aspects of accessibility. Comprehensive evaluation to determine if a website meets all accessibility guidelines takes more effort.

    There are evaluation tools that help with evaluation. However, no tool alone can determine if a site meets accessibility guidelines. Knowledgeable human evaluation is required to determine if a site is accessible.

    hashtag
    Examples

    hashtag
    Alternative Text for Images

    image of logo; HTML markup img alt='Web Accessibility Initiative logo'

    Images should include equivalent alternative textarrow-up-right (alt text) in the markup/code.

    If alt text isn’t provided for images, the image information is inaccessible, for example, to people who cannot see and use a screen reader that reads aloud the information on a page, including the alt text for the visual image.

    When equivalent alt text is provided, the information is available to people who are blind, as well as to people who turn off images (for example, in areas with expensive or low bandwidth). It’s also available to technologies that cannot see images, such as search engines.

    hashtag
    Keyboard Input

    mouse crossed out

    Some people cannot use a mouse, including many older users with limited fine motor control. An accessible website does not rely on the mouse; it makes all functionality available from a keyboardarrow-up-right. Then people with disabilities can use assistive technologiesarrow-up-right that mimic the keyboard, such as speech input.

    hashtag
    Transcripts for Audio

    Just as images aren’t available to people who can’t see, audio files aren’t available to people who can’t hear. Providing a text transcript makes the audio information accessible to people who are deaf or hard of hearing, as well as to search engines and other technologies that can’t hear.

    It’s easy and relatively inexpensive for websites to provide transcripts. There are also transcription servicesarrow-up-right that create text transcripts in HTML format.

    hashtag
    For More Information

    W3C WAI provides a wide range of resources on different aspects of web accessibility standardsarrow-up-right, educationarrow-up-right, testing/evaluationarrow-up-right, project management, and policyarrow-up-right. We encourage you to explore this website, or look through the WAI Resourcesarrow-up-right list.

    hashtag

    Excerpt

    WCAG 2.1 guidelines and success criteria are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc. They are stable and do not change.


    WCAG 2.1 guidelines and success criteria are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc. They are stable and do not change.

    Specific guidance for authors and evaluators on meeting the WCAG success criteria is provided in techniques, which include code examples, resources, and tests. W3C's Techniques for WCAG 2.1arrow-up-right document is updated periodically, about twice per year, to cover more current best practices and changes in technologies and tools.

    The three types of guidance in Techniques for WCAG 2.1arrow-up-right are explained below:

    • Sufficient techniques

    • Advisory techniques

    • Failures

    Also explained below:

    • General and technology-specific techniques - which can be sufficient or advisory

    • Other techniques - beyond what is in W3C's published document

    • Technique tests

    • User agent and assistive technology support

    • Using the techniques - with important considerations

    Understanding Conformancearrow-up-right provides related information, including on understanding accessibility supportarrow-up-right.

    hashtag
    Techniques are Informative

    Techniques are informative—that means they are not required. The basis for determining conformance to WCAG 2.1 is the success criteria from the WCAG 2.1 standard—not the techniques.

    Note 1: W3C cautions against requiring W3C's sufficient techniques. The only thing that should be required is meeting the WCAG 2.1 success criteria. To learn more, see:

    • What would be the negative consequences of allowing only W3C's published techniques to be used for conformance to WCAG 2.1?arrow-up-right in the WCAG 2 FAQ

    Note 2: Techniques for WCAG 2.1arrow-up-right uses the words "must" and "should" only to clarify guidance within the techniques, not to convey requirements for WCAG.

    hashtag
    Sufficient Techniques

    Sufficient techniques are reliable ways to meet the success criteria.

    • From an author's perspective: If you use the sufficient techniques for a given criterion correctly and it is accessibility-supportedarrow-up-right for your users, you can be confident that you met the success criterion.

    • From an evaluator's perspective: If web content implements the sufficient techniques for a given criterion correctly and it is accessibility-supportedarrow-up-right for the content's users, it conforms to that success criterion. (The converse is not true; if content does not implement these sufficient techniques, it does not necessarily fail the success criteria, as explained in Testing Techniquesarrow-up-right below.)

    There may be other ways to meet success criteria besides the sufficient techniques in W3C's Techniques for WCAG 2.1arrow-up-right document, as explained in Other Techniquesarrow-up-right below. (See also Techniques are Informativearrow-up-right above.)

    hashtag
    Numbered Lists, "AND"

    The W3C-documented sufficient techniques are provided in a numbered list where each list item provides a technique or combination of techniques that can be used to meet the success criterion. Where there are multiple techniques on a numbered list item connected by "AND" then all of the techniques must be used to be sufficient. For example, Sufficient Techniques for 1.3.1arrow-up-right has: "G115: Using semantic elements to mark up structure AND H49: Using semantic markup to mark emphasized or special text (HTML)".

    hashtag
    Advisory Techniques

    Advisory techniques are suggested ways to improve accessibility. They are often very helpful to some users, and may be the only way that some users can access some types of content.

    Advisory techniques are not designated as sufficient techniques for various reasons such as:

    • they may not be sufficient to meet the full requirements of the success criteria;

    • they may be based on technology that is not yet stable;

    • they may not be accessibility supportedarrow-up-right in many cases (for example, assistive technologies do not work with them yet);

    • they may not be testable;

    • in some circumstances they may not be applicable or practical, and may even decrease accessibility for some users while increasing it for others;

    • they may not address the success criterion itself, and instead provide related accessibility benefits.

    Authors are encouraged to apply all of the techniques where appropriate to best address the widest range of users' needs.

    hashtag
    Failures

    Failures are things that cause accessibility barriers and fail specific success criteria. The documented failures are useful for:

    • Authors to know what to avoid,

    • Evaluators to use for checking if content does not meet WCAG success criteria.

    Content that has a failure does not meet WCAG success criteria, unless an alternate version is provided without the failure.

    If anyone identifies a situation where a documented failure is not correct, please report the situation as a WCAG commentarrow-up-right so that it can be corrected or deleted as appropriate.

    hashtag
    General and Technology-specific Techniques

    General techniques describe basic practices that apply to all technologies. Technology-specific techniques apply to a specific technology.

    Some success criteria do not have technology-specific techniques and are covered only with general techniques. Therefore, both the general techniques and the relevant technology-specific techniques should be considered.

    Publication of techniques for a specific technology does not imply that the technology can be used in all situations to create content that meets WCAG 2.1 success criteria and conformance requirements. Developers need to be aware of the limitations of specific technologies and provide content in a way that is accessible to people with disabilities.

    hashtag
    Other Techniques

    In addition to the techniques in W3C's Techniques for WCAG 2.1arrow-up-right document, there are other ways to meet WCAG success criteria. W3C's techniques are not comprehensive and may not cover newer technologies and situations.

    Web content does not have to use W3C's published techniques in order to conform to WCAG 2.1.__(See also Techniques are Informativearrow-up-right above.)

    Content authors can develop different techniques. For example, an author could develop a technique for HTML5, WAI-ARIAarrow-up-right, or other new technology. Other organizations may develop sets of techniques to meet WCAG 2.1 success criteria.

    Any techniques can be sufficient if:

    • they satisfy the success criterion, and

    • all of the WCAG 2.1 conformance requirementsarrow-up-right are met.

    hashtag
    Submitting Techniques

    The WCAG Working Group encourages people to submit new techniques so that they can be considered for inclusion in updates of the Techniques for WCAG 2.1arrow-up-right document. Please submit techniques for consideration using the Techniques Submission Formarrow-up-right.

    hashtag
    Testing Techniques

    Each technique has tests that help:

    • authors verify that they implemented the technique properly, and

    • evaluators determine if web content meets the technique.

    The tests are only for a technique, they are not tests for conformance to WCAG success criteria.

    • Failing a technique test does not necessarily mean failing WCAG, because the techniques are discrete (that is, they address one specific point) and they are not required.

    • Content can meet WCAG success criteria in different ways other than W3C's published sufficient techniques.

    • Content that passes the sufficient techniques for a specific technology does not necessarily meet all WCAG success criteria. Some success criteria have only general techniques, not technology-specific techniques.

    • The content must be for the content's users. Some sufficient techniques require browser, assistive technology, or other support that some users might not have.

    Thus while the techniques are useful for evaluating content, evaluations must go beyond just checking the sufficient technique tests in order to evaluate how content conforms to WCAG success criteria.

    Failures are particularly useful for evaluations because they do indicate non-conformance (unless an alternate version is provided without the failure).

    hashtag
    User Agent and Assistive Technology Support Notes

    Some techniques require that web content users have specific browsers or assistive technologies in order for the technique to be accessibility-supportedarrow-up-right. The User Agent and Assistive Technology Support Notes sections of individual techniques include some information to help determine accessibility support.

    hashtag
    Support Notes Change Over Time

    As time passes, the versions of user agents (browsers, etc.) or assistive technologies listed may not be the current versions. The Working Group may not update most of these notes as new versions are released. Authors should test techniques with the user agents and assistive technologies currently available to their users. See also Understanding Accessibility Supportarrow-up-right.

    hashtag
    Using the Techniques

    Techniques for WCAG 2.1arrow-up-right is not intended to be used as a stand-alone document. Instead, it is expected that content authors will usually use How to Meet WCAG 2.1: A customizable quick referencearrow-up-right to read the WCAG success criteria, and follow links from there to specific topics in Understanding WCAG 2.1 and to specific techniques.

    hashtag
    Alternatives must meet success criteria

    Some techniques describe how to provide alternate ways for users to get content. For example, G73: Providing a long description in another location with a link to it that is immediately adjacent to the non-text contentarrow-up-right mentions a transcript as an alternative for an audio file. Some alternatives must also conform to WCAG. For example, the transcript itself must meet all relevant success criteria.

    hashtag
    Example Code

    The code examples in the techniques are intended to demonstrate only the specific point discussed in the technique. They might not demonstrate best practice for other aspects of accessibility, usability, or coding not related to the technique. They are not intended to be copied and used as the basis for developing web content.

    Many techniques point to "working examples" that are more robust and may be appropriate for copying and integrating into web content.

    hashtag

    Cheatsheet

    Get the printable cheat sheetarrow-up-right

    A short guide to all the exported functions in React Testing Library

    • render const {/* */} = render(Component) returns:

      • unmount function to unmount the component

      • container reference to the DOM node where the component is mounted

      • all the queries from DOM Testing Library, bound to the document so there is no need to pass a node as the first argument (usually, you can use the screen import instead)

    hashtag
    Queries

    Difference from DOM Testing Library

    The queries returned from render in React Testing Library are the same as DOM Testing Library except they have the first argument bound to the document, so instead of getByText(node, 'text') you do getByText('text')

    See Which query should I use?

    No Match
    1 Match
    1+ Match
    Await?
    • ByLabelText find by label or aria-label text content

      • getByLabelText

      • queryByLabelText

    hashtag
    Async

    The dom-testing-library Async API is re-exported from React Testing Library.

    • waitFor (Promise) retry the function within until it stops throwing or times out

    • waitForElementToBeRemoved (Promise) retry the function until it no longer returns a DOM node

    hashtag
    Events

    See Events API

    • fireEvent trigger DOM event: fireEvent(node, event)

    • fireEvent.* helpers for default event types

      • click

    hashtag
    Other

    See Querying Within Elements, Config API, Cleanup,

    • within take a node and return an object with all the queries bound to the node (used to return the queries from React Testing Library's render method): within(node).getByText("hello")

    • configure change global options: configure({testIdAttribute: 'my-data-test-id'})

    hashtag
    Text Match Options

    Given the following HTML:

    Will** find the div:**

    Example

    hashtag
    Full Example

    See the following sections for a detailed breakdown of the test

    We recommend using Mock Service Workerarrow-up-right library to declaratively mock API communication in your tests instead of stubbing window.fetch, or relying on third-party adapters.


    hashtag
    Step-By-Step

    hashtag
    Imports

    hashtag
    Mock

    Use the setupServer function from msw to mock an API request that our tested component makes.

    hashtag
    Arrange

    The render method renders a React element into the DOM.

    hashtag
    Act

    The fireEvent method allows you to fire events to simulate user actions.

    hashtag
    Assert

    hashtag
    System Under Test

    JEST

    hashtag
    The problem

    You want to use [jest][] to write tests that assert various things about the state of a DOM. As part of that goal, you want to avoid all the repetitive patterns that arise in doing so. Checking for an element's attributes, its text content, its css classes, you name it.

    hashtag
    This solution

    The @testing-library/jest-dom library provides a set of custom jest matchers that you can use to extend jest. These will make your tests more declarative, clear to read and to maintain.

    hashtag
    Table of Contents

    • Installation

    • Usage

      • With TypeScript

    hashtag
    Installation

    This module is distributed via [npm][npm] which is bundled with [node][node] and should be installed as one of your project's devDependencies:

    or

    for installation with package manager.

    Note: We also recommend installing the jest-dom eslint plugin which provides auto-fixable lint rules that prevent false positive tests and improve test readability by ensuring you are using the right matchers in your tests. More details can be found at .

    hashtag
    Usage

    Import @testing-library/jest-dom once (for instance in your ) and you're good to go:

    hashtag
    With TypeScript

    If you're using TypeScript, make sure your setup file is a .ts and not a .js to include the necessary types.

    You will also need to include your setup file in your tsconfig.json if you haven't already:

    hashtag
    Custom matchers

    @testing-library/jest-dom can work with any library or framework that returns DOM elements from queries. The custom matcher examples below are written using matchers from @testing-library's suite of libraries (e.g. getByTestId, queryByTestId, getByText, etc.)

    hashtag
    toBeDisabled

    This allows you to check whether an element is disabled from the user's perspective. According to the specification, the following elements can be : button, input, select, textarea, optgroup, option, fieldset, and custom elements.

    This custom matcher considers an element as disabled if the element is among the types of elements that can be disabled (listed above), and the disabled attribute is present. It will also consider the element as disabled if it's inside a parent form element that supports being disabled and has the disabled attribute present.

    Examples

    This custom matcher does not take into account the presence or absence of the aria-disabled attribute. For more on why this is the case, check .


    hashtag
    toBeEnabled

    This allows you to check whether an element is not disabled from the user's perspective.

    It works like not.toBeDisabled(). Use this matcher to avoid double negation in your tests.

    This custom matcher does not take into account the presence or absence of the aria-disabled attribute. For more on why this is the case, check .


    hashtag
    toBeEmptyDOMElement

    This allows you to assert whether an element has no visible content for the user. It ignores comments but will fail if the element contains white-space.

    Examples


    hashtag
    toBeInTheDocument

    This allows you to assert whether an element is present in the document or not.

    Examples

    Note: This matcher does not find detached elements. The element must be added to the document to be found by toBeInTheDocument. If you desire to search in a detached element please use: toContainElement


    hashtag
    toBeInvalid

    This allows you to check if an element, is currently invalid.

    An element is invalid if it has an with no value or a value of "true", or if the result of is false.

    Examples


    hashtag
    toBeRequired

    This allows you to check if a form element is currently required.

    An element is required if it is having a required or aria-required="true" attribute.

    Examples


    hashtag
    toBeValid

    This allows you to check if the value of an element, is currently valid.

    An element is valid if it has no s or an attribute value of "false". The result of must also be true if it's a form element.

    Examples


    hashtag
    toBeVisible

    This allows you to check if an element is currently visible to the user.

    An element is visible if all the following conditions are met:

    • it is present in the document

    • it does not have its css property display set to none

    • it does not have its css property visibility

    Examples


    hashtag
    toContainElement

    This allows you to assert whether an element contains another element as a descendant or not.

    Examples


    hashtag
    toContainHTML

    Assert whether a string representing a HTML element is contained in another element. The string should contain valid html, and not any incomplete html.

    Examples

    Chances are you probably do not need to use this matcher. We encourage testing from the perspective of how the user perceives the app in a browser. That's why testing against a specific DOM structure is not advised.

    It could be useful in situations where the code being tested renders html that was obtained from an external source, and you want to validate that that html code was used as intended.

    It should not be used to check DOM structure that you control. Please use toContainElement instead.


    hashtag
    toHaveAccessibleDescription

    This allows you to assert that an element has the expected .

    You can pass the exact string of the expected accessible description, or you can make a partial match passing a regular expression, or by using /.

    Examples


    hashtag
    toHaveAccessibleName

    This allows you to assert that an element has the expected . It is useful, for instance, to assert that form elements and buttons are properly labelled.

    You can pass the exact string of the expected accessible name, or you can make a partial match passing a regular expression, or by using /.

    Examples


    hashtag
    toHaveAttribute

    This allows you to check whether the given element has an attribute or not. You can also optionally check that the attribute has a specific expected value or partial match using /

    Examples


    hashtag
    toHaveClass

    This allows you to check whether the given element has certain classes within its class attribute.

    You must provide at least one class, unless you are asserting that an element does not have any classes.

    Examples


    hashtag
    toHaveFocus

    This allows you to assert whether an element has focus or not.

    Examples


    hashtag
    toHaveFormValues

    This allows you to check if a form or fieldset contains form controls for each given name, and having the specified value.

    It is important to stress that this matcher can only be invoked on a or a element.

    This allows it to take advantage of the property in form and fieldset to reliably fetch all form controls within them.

    This also avoids the possibility that users provide a container that contains more than one form, thereby intermixing form controls that are not related, and could even conflict with one another.

    This matcher abstracts away the particularities with which a form control value is obtained depending on the type of form control. For instance, <input> elements have a value attribute, but <select> elements do not. Here's a list of all cases covered:

    • <input type="number"> elements return the value as a number, instead of a string.

    • <input type="checkbox"> elements:

    The above rules make it easy, for instance, to switch from using a single select control to using a group of radio buttons. Or to switch from a multi select control, to using a group of checkboxes. The resulting set of form values used by this matcher to compare against would be the same.

    Examples

    hashtag
    toHaveStyle

    This allows you to check if a certain element has some specific css properties with specific values applied. It matches only if the element has all the expected properties applied, not just some of them.

    Examples

    This also works with rules that are applied to the element via a class name for which some rules are defined in a stylesheet currently active in the document. The usual rules of css precedence apply.


    hashtag
    toHaveTextContent

    This allows you to check whether the given node has a text content or not. This supports elements, but also text nodes and fragments.

    When a string argument is passed through, it will perform a partial case-sensitive match to the node content.

    To perform a case-insensitive match, you can use a RegExp with the /i modifier.

    If you want to match the whole content, you can use a RegExp to do it.

    Examples


    hashtag
    toHaveValue

    This allows you to check whether the given form element has the specified value. It accepts <input>, <select> and <textarea> elements with the exception of <input type="checkbox"> and <input type="radio">, which can be meaningfully matched only using toBeChecked or toHaveFormValues.

    For all other form elements, the value is matched using the same algorithm as in toHaveFormValues does.

    Examples

    Using DOM Testing Library


    hashtag
    toHaveDisplayValue

    This allows you to check whether the given form element has the specified displayed value (the one the end user will see). It accepts <input>, <select> and <textarea> elements with the exception of <input type="checkbox"> and <input type="radio">, which can be meaningfully matched only using toBeChecked or toHaveFormValues.

    Examples

    Using DOM Testing Library


    hashtag
    toBeChecked

    This allows you to check whether the given element is checked. It accepts an input of type checkbox or radio and elements with a role of checkbox, radio or switch with a valid aria-checked attribute of "true" or "false".

    Examples


    hashtag
    toBePartiallyChecked

    This allows you to check whether the given element is partially checked. It accepts an input of type checkbox and elements with a role of checkbox with a aria-checked="mixed", or input of type checkbox with indeterminate set to true

    Examples


    hashtag
    toHaveErrorMessage

    This allows you to check whether the given element has an or not.

    Use the aria-errormessage attribute to reference another element that contains custom error message text. Multiple ids is NOT allowed. Authors MUST use aria-invalid in conjunction with aria-errormessage. Learn more from .

    Whitespace is normalized.

    When a string argument is passed through, it will perform a whole case-sensitive match to the error message text.

    To perform a case-insensitive match, you can use a RegExp with the /i modifier.

    To perform a partial match, you can pass a RegExp or use expect.stringContaining("partial string").

    Examples

    hashtag
    Deprecated matchers

    hashtag
    toBeEmpty

    Note: This matcher is being deprecated due to a name clash with jest-extended. See more info in #216. In the future, please use only toBeEmptyDOMElement

    This allows you to assert whether an element has content or not.

    Examples


    hashtag
    toBeInTheDOM

    This custom matcher is deprecated. Prefer toBeInTheDocument instead.

    This allows you to check whether a value is a DOM element, or not.

    Contrary to what its name implies, this matcher only checks that you passed to it a valid DOM element. It does not have a clear definition of what "the DOM" is. Therefore, it does not check whether that element is contained anywhere.

    This is the main reason why this matcher is deprecated, and will be removed in the next major release. You can follow the discussion around this decision in more detail .

    As an alternative, you can use toBeInTheDocument or toContainElement. Or if you just want to check if a value is indeed an HTMLElement you can always use some of :

    Note: The differences between toBeInTheDOM and toBeInTheDocument are significant. Replacing all uses of toBeInTheDOM with toBeInTheDocument will likely cause unintended consequences in your tests. Please make sure when replacing toBeInTheDOM to read through the documentation of the proposed alternatives to see which use case works better for your needs.

    hashtag
    toHaveDescription

    This custom matcher is deprecated. Prefer toHaveAccessibleDescription instead, which is more comprehensive in implementing the official spec.

    This allows you to check whether the given element has a description or not.

    An element gets its description via the . Set this to the id of one or more other elements. These elements may be nested inside, be outside, or a sibling of the passed in element.

    Whitespace is normalized. Using multiple ids will .

    When a string argument is passed through, it will perform a whole case-sensitive match to the description text.

    To perform a case-insensitive match, you can use a RegExp with the /i modifier.

    To perform a partial match, you can pass a RegExp or use expect.stringContaining("partial string").

    Examples

    hashtag
    Inspiration

    This whole library was extracted out of Kent C. Dodds' [DOM Testing Library][dom-testing-library], which was in turn extracted out of [React Testing Library][react-testing-library].

    The intention is to make this available to be used independently of these other libraries, and also to make it more clear that these other libraries are independent from jest, and can be used with other tests runners as well.

    hashtag
    Other Solutions

    I'm not aware of any, if you are please [make a pull request][prs] and add it here!

    If you would like to further test the accessibility and validity of the DOM consider . It doesn't overlap with jest-dom but can complement it for more in-depth accessibility checking (eg: validating aria attributes or ensuring unique id attributes).

    hashtag
    Guiding Principles

    [The more your tests resemble the way your software is used, the more confidence they can give you.][guiding-principle]

    This library follows the same guiding principles as its mother library [DOM Testing Library][dom-testing-library]. Go [check them out][guiding-principle] for more details.

    Additionally, with respect to custom DOM matchers, this library aims to maintain a minimal but useful set of them, while avoiding bloating itself with merely convenient ones that can be easily achieved with other APIs. In general, the overall criteria for what is considered a useful custom matcher to add to this library, is that doing the equivalent assertion on our own makes the test code more verbose, less clear in its intent, and/or harder to read.

    ByTitle

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByTitle, queryByTitle, getAllByTitle, queryAllByTitle, findByTitle, findAllByTitle

    hashtag
    API

    getByTitle(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      title: TextMatch,
      options?: {
        exact?: boolean = true,
        normalizer?: NormalizerFn,
      }): HTMLElement

    Returns the element that has the matching title attribute.

    Will also find a title element within an SVG.

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    hashtag
    Options

    TextMatch options

    Migrate from Enzyme

    This page is intended for developers who have experience with Enzyme and are trying to understand how to migrate to React Testing Library. It does not go into great detail about how to migrate all types of tests, but it does have some helpful information for those who are comparing Enzyme with React Testing Library.

    hashtag
    What is React Testing Library?

    React Testing Library is part of an open-source project named Testing Libraryarrow-up-right. There are several other helpful tools and libraries in the Testing Library project which you can use to write more concise and useful tests. Besides React Testing Library, here are some of the project's other libraries that can help you along the way:

    • : jest-dom provides a set of custom Jest matchers that you can use to extend Jest. These make your tests more declarative, clearer to read, and easier to maintain.

    • : user-event tries to simulate the real events that happen in the browser as the user interacts with elements on the page. For example, userEvent.click(checkbox) would change the state of the checkbox.

    hashtag
    Why should I use React Testing Library?

    Enzyme is a powerful test library, and its contributors did a lot for the JavaScript community. In fact, many of the React Testing Library maintainers used and contributed to Enzyme for years before developing and working on React Testing Library. So we want to say thank you to the contributors of Enzyme!

    The primary purpose of React Testing Library is to increase confidence in your tests by testing your components in the way a user would use them. Users don't care what happens behind the scenes, they just see and interact with the output. Instead of accessing the components' internal APIs or evaluating their state, you'll get more confidence by writing your tests based on the component output.

    React Testing Library aims to solve the problem that many developers face when writing tests with Enzyme, which allows (and encourages) developers to test . Tests which do this ultimately prevent you from modifying and refactoring the component without changing its tests. As a result, the tests slow down development speed and productivity. Every small change may require rewriting some part of your tests, even if the change does not affect the component's output.

    Rewriting your tests in React Testing library is worthwhile because you'll be trading tests that slow you down for tests that give you more confidence and increase your productivity in the long run.

    hashtag
    How to migrate from Enzyme to React Testing Library?

    To ensure a successful migration, we recommend doing it incrementally by running the two test libraries side by side in the same application, porting your Enzyme tests to React Testing Library one by one. That makes it possible to migrate even large and complex applications without disrupting other business because the work can be done collaboratively and spread out over time.

    hashtag
    Install React Testing Library

    First, install React Testing Library and the jest-dom helper library (you can check this page for the complete installation and setup guide).

    hashtag
    Import React Testing Library to your test

    If you're using Jest (you can use other test frameworks), then you only need to import the following modules into your test file:

    The test structure can be the same as you would write with Enzyme:

    Note: you can also use describe and it blocks with React Testing Library. React Testing Library doesn't replace Jest, just Enzyme. We recommend test because it helps with this: .

    hashtag
    Basic Enzyme to React Testing Library migration examples

    One thing to keep in mind is that there's not a one-to-one mapping of Enzyme features to React Testing Library features. Many Enzyme features result in inefficient tests anyway, so some of the features you're accustomed to with Enzyme need to be left behind (no more need for a wrapper variable or wrapper.update() calls, etc.).

    React Testing Library has helpful queries which let you access your component's elements and their properties. We'll show some typical Enzyme tests along with alternatives using React Testing Library.

    Let's say we have a Welcome component which shows a welcome message. We will have a look at both Enzyme and React Testing Library tests to learn how we can test this component:

    React Component

    The following component gets a name from props and shows a welcome message in an h1 element. It also has a text input which users can change to a different name, and the template updates accordingly. Check the live version on .

    hashtag
    Test 1: Render the component, and check if the h1 value is correct

    Enzyme test

    React Testing library

    As you can see, the tests are pretty similar. Enzyme's shallow renderer doesn't render sub-components, so React Testing Library's render method is more similar to Enzyme's mount method.

    In React Testing Library, you don't need to assign the render result to a variable (i.e. wrapper). You can simply access the rendered output by calling functions on the screen object. The other good thing to know is that React Testing Library automatically cleans up the environment after each test so you don't need to call cleanup in an afterEach or beforeEach function.

    The other thing that you might notice is getByRole which has 'heading' as its argument. 'heading' is the accessible role of the h1 element. You can learn more about them on the queries documentation page. One of the things people quickly learn to love about Testing Library is how it encourages you to write more accessible applications (because if it's not accessible, then it's harder to test).

    hashtag
    Test 2: Input texts must have correct value

    In the component above, the input values are initialized with the props.firstName and props.lastName values. We need to check whether the value is correct or not.

    Enzyme

    React Testing Library

    Cool! It's pretty simple and handy, and the tests are clear enough that we don't need to talk much about them. Something that you might notice is that the <form> has a role="form" attribute, but what is it?

    role is one of the accessibility-related attributes that is recommended to use to improve your web application for people with disabilities. Some elements have default role values and you don't need to set one for them, but some others like <div> do not have default role values. You can use different approaches to access the <div> element, but we recommend trying to access elements by their implicit role to make sure your component is accessible by people with disabilities and those using screen readers. This section of the query documentation might help you understand the concepts better.

    A <form> element must have a name attribute in order to have an implicit role of 'form' (as required by the specification).

    React Testing Library aims to test the components how users use them. Users see buttons, headings, forms and other elements by their role, not by their id, class, or element tag name. Therefore, when you use React Testing Library you should avoid accessing the DOM with the document.querySelector API. (You can use it in your tests, but it's not recommended for the reasons stated in this paragraph.)

    React Testing Library exposes some handy query APIs which help you access the component elements efficiently. You can see the list of available queries here. If you're not sure which query you should use in a given situation, we have a great page which explains which query to use, so check it out!

    If you still have a question about which of React Testing Library's queries to use, then check out and the accompanying Chrome extension which aims to enable developers to find the best query when writing tests. It also helps you find the best queries to select elements. It allows you to inspect the element hierarchies in the Chrome Developer Tools and provides you with suggestions on how to select them, all while encouraging good testing practices.

    hashtag
    Using act() and wrapper.update()

    When testing asynchronous code in Enzyme, you usually need to call act() to run your tests correctly. When using React Testing Library, you don't need to explicitly call act() most of the time because it wraps API calls with act() by default.

    update() syncs the Enzyme component tree snapshot with the React component tree, so you may see wrapper.update() in Enzyme tests. React Testing Library does not have (or need) a similar method, which is good for you since you need to handle fewer things!

    hashtag
    Simulate user events

    There are two ways to simulate user events with React Testing Library. One way is to use the user-event library, and the other way is to use fireEvent which is included in React Testing Library. user-event is actually built on top of fireEvent (which simply calls dispatchEvent on the given element). user-event is generally recommended because it ensures that all the events are fired in the correct order for typical user interactions. This helps ensure your tests resemble the way your software is actually used.

    To use the @testing-library/user-event module, first install it:

    Now you can import it into your test:

    To demonstrate how to use the user-event library, imagine we have a Checkbox component which shows a checkbox input and an associated label. We want to simulate the event of a user clicking the checkbox:

    We want to test that when a user clicks on the checkbox's associated label, the input's "checked" property is properly set. Let's see how we might write a test for that case:

    Nice!

    hashtag
    Triggering class methods in tests (wrapper.instance())

    As we already discussed, we recommend against testing implementation details and things that users will not be aware of. We aim to test and interact with the component more like how our users would.

    If your test uses `instance()` or `state()`, know that you're testing things that the user couldn't possibly know about or even care about, which will take your tests further from giving you confidence that things will work when your user uses them. —

    If you're unsure how to test something internal to your component, then take a step back and consider: "What would the user do to trigger this code to run?" Then make your test do that.

    hashtag
    How to shallow render a component?

    In general, you should avoid mocking out components. However, if you need to, then try using . For more information, see the FAQ.

    Testing Input

    hashtag
    🎹 Testing Input

    hashtag
    🎹 Testing Input

    hashtag
    Input Event

    Note

    If you want to simulate a more natural typing behaviour while testing your component, consider the companion library [user-event](https: //testing-library.com/docs/ecosystem-user-event)

    hashtag
    🗺 Site Navigation

    hashtag
    📖 Storybook

    Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

    It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

    To get it up and running navigate to the app repo and, from the command line, enter:

    Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

    hashtag
    Stories

    A story, much like a React component, is a function that describes how to render a component.

    You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

    For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

    So here's how the initial default PushDownPanel might look:

    And then we can add variations based on different states, for example a version that uses icons:

    And the new stories will show up in the sidebar navigation, like so:

    hashtag
    Args

    Story definitions can be further improved to take advantage of Storybook's "args" concept.

    A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

    Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

    hashtag
    Controls

    Controls allow designers and developers to explore component behavior by mucking about with its arguments.

    Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

    This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

    Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

    A select menu to control the number of items that render:

    And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

    hashtag
    A11y and Other Addons

    Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

    One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

    We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

    hashtag
    Anatomy of a Story

    Stories exist alongside the other component files as stories.js.

    Here's an example of how a typical story might take shape:

    Let's break this down line by line:

    Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

    Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

    Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

    It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

    Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

    We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

    In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

    Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

    We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

    And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

    hashtag
    storybookCustomArgs

    The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

    We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

    hashtag
    createStoryOptions

    Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

    Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

    hashtag
    Resources

    • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

    • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

    • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

    Self Link

    Duke Training

    Website Navigation


    Table of contents

    hashtag
    General Info

    hashtag
    Sitecore

    Enums

    hashtag
    Enums

    Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.

    Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

    hashtag
    Numeric enums

    We'll first start off with numeric enums, which are probably more familiar if you're coming from other languages. An enum can be defined using the enum keyword.

    Above, we have a numeric enum where Up is initialized with 1 . All of the following members are auto-incremented from that point on. In other words, Direction.Up has the value 1 , Down has 2 , Left has 3 , and Right has 4 .

    If we wanted, we could leave off the initializers entirely:

    Here, Up would have the value 0 , Down would have 1 , etc. This auto-incrementing behavior is useful for cases where we might not care about the member values themselves, but do care that each value is distinct from other values in the same enum.

    Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum:

    Numeric enums can be mixed in [computed and constant members (see below)](https: //www.typescriptlang.org/docs/handbook/enums.html#computed-and- constant-members). The short story is, enums without initializers either need to be first, or have to come after numeric enums initialized with numeric constants or other constant enum members. In other words, the following isn't allowed:

    hashtag
    String enums

    String enums are a similar concept, but have some subtle [runtime differences](https: //www.typescriptlang.org/docs/handbook/enums.html#enums-at-runtime) as documented below. In a string enum, each member has to be constant-initialized with a string literal, or with another string enum member.

    While string enums don't have auto-incrementing behavior, string enums have the benefit that they "serialize" well. In other words, if you were debugging and had to read the runtime value of a numeric enum, the value is often opaque - it doesn't convey any useful meaning on its own (though [reverse mapping](https: //www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings) can often help). String enums allow you to give a meaningful and readable value when your code runs, independent of the name of the enum member itself.

    hashtag
    Heterogeneous enums

    Technically enums can be mixed with string and numeric members, but it's not clear why you would ever want to do so:

    Unless you're really trying to take advantage of JavaScript's runtime behavior in a clever way, it's advised that you don't do this.

    hashtag
    Computed and

    constant members

    Each enum member has a value associated with it which can be either _ constant_ or computed. An enum member is considered constant if:

    • It is the first member in the enum and it has no initializer, in which case it's assigned the value 0:

    • The enum member is initialized with a constant enum expression. A constant enum expression is a subset of TypeScript expressions that can be fully evaluated at compile time. An expression is a constant enum expression if it is:

      1. a literal enum expression (basically a string literal or a numeric literal)

      2. a reference to previously defined constant enum member (which can originate from a different enum)

    In all other cases enum member is considered computed.

    hashtag
    Union enums and enum member types

    There is a special subset of constant enum members that aren't calculated: literal enum members. A literal enum member is a constant enum member with no initialized value, or with values that are initialized to

    • any string literal (e.g. "foo", "bar, "baz")

    • any numeric literal (e.g. 1, 100)

    When all members in an enum have literal enum values, some special semantics come into play.

    The first is that enum members also become types as well! For example, we can say that certain members can only have the value of an enum member:

    The other change is that enum types themselves effectively become a union of each enum member. With union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in the enum itself. Because of that, TypeScript can catch bugs where we might be comparing values incorrectly. For example:

    In that example, we first checked whether x was not E.Foo . If that check succeeds, then our || will short-circuit, and the body of the ‘if' will run. However, if the check didn't succeed, then x can only be E.Foo , so it doesn't make sense to see whether it's equal to E.Bar .

    hashtag
    Enums at runtime

    Enums are real objects that exist at runtime. For example, the following enum

    can actually be passed around to functions

    hashtag
    Enums at compile time

    Even though Enums are real objects that exist at runtime, the keyof keyword works differently than you might expect for typical objects. Instead, use keyof typeof to get a Type that represents all Enum keys as strings.

    hashtag
    Reverse mappings

    In addition to creating an object with property names for members, numeric enums members also get a reverse mapping from enum values to enum names. For example, in this example:

    TypeScript compiles this down to the following JavaScript:

    In this generated code, an enum is compiled into an object that stores both forward ( name -> value ) and reverse ( value -> name ) mappings. References to other enum members are always emitted as property accesses and never inlined.

    Keep in mind that string enum members do not get a reverse mapping generated at all.

    hashtag
    `

    const` enums

    In most cases, enums are a perfectly valid solution. However sometimes requirements are tighter. To avoid paying the cost of extra generated code and additional indirection when accessing enum values, it's possible to use const enums. const enums are defined using the const modifier on our enums:

    const enums can only use constant enum expressions and unlike regular enums they are completely removed during compilation. const enum members are inlined at use sites. This is possible since const enums cannot have computed members.

    in generated code will become

    ** const enum pitfalls**

    Inlining enum values is straightforward at first, but comes with subtle implications. These pitfalls pertain to ambient const enums only (basically const enums in .d.ts files) and sharing them between projects, but if you are publishing or consuming .d.ts files, these pitfalls likely apply to you, because tsc --declaration transforms .ts files into .d.ts files.

    1. For the reasons laid out in the [isolatedModules documentation](https: //www.typescriptlang.org/tsconfig#references-to- const-enum-members), that mode is fundamentally incompatible with ambient const enums. This means if you publish ambient const enums, downstream consumers will not be able to use [isolatedModules](https: //www.typescriptlang.org/tsconfig#isolatedModules) and those enum values at the same time.

    2. You can easily inline values from version A of a dependency at compile time, and import version B at runtime. Version A and B's enums can have different values, if you are not very careful, resulting in [surprising bugs](https: //github.com/microsoft/TypeScript/issues/5219#issue-110947903), like taking the wrong branches of if statements. These bugs are especially pernicious because it is common to run automated tests at roughly the same time as projects are built, with the same dependency versions, which misses these bugs completely.

    Here are two approaches to avoiding these pitfalls:

    A. Do not use const enums at all. You can easily [ban const enums](https: //github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#how-can-i-ban-specific-language-feature) with the help of a linter. Obviously this avoids any issues with const enums, but prevents your project from inlining its own enums. Unlike inlining enums from other projects, inlining a project's own enums is not problematic and has performance implications. B. Do not publish ambient const enums, by de constifying them with the help of [preserve constEnums ](https: //www.typescriptlang.org/tsconfig#preserve constEnums). This is the approach taken internally by the [TypeScript project itself](https: //github.com/microsoft/TypeScript/pull/5422). [preserve constEnums ](https: //www.typescriptlang.org/tsconfig#preserve constEnums)emits the same JavaScript for const enums as plain enums. You can then safely strip the const modifier from .d.ts files [in a build step](https: //github.com/microsoft/TypeScript/blob/1a981d1df1810c868a66b3828497f049a944951c/Gulpfile.js#L144).

    This way downstream consumers will not inline enums from your project, avoiding the pitfalls above, but a project can still inline its own enums, unlike banning const enums entirely.

    hashtag
    Ambient enums

    Ambient enums are used to describe the shape of already existing enum types.

    One important difference between ambient and non-ambient enums is that, in regular enums, members that don't have an initializer will be considered constant if its preceding enum member is considered constant. By contrast, an ambient (and non- const) enum member that does not have an initializer is always considered computed.

    hashtag
    Objects vs Enums

    In modern TypeScript, you may not need an enum when an object with as const could suffice:

    \

    ACCESSIBILITY

    Accessible Rich Internet Applications (ARIA) is a set of attributes that define ways to make web content and web applications (especially those developed with JavaScript) more accessible to people wit

    hashtag
    ARIA - Accessibility

    Excerpt

    Accessible Rich Internet Applications (ARIA) is a set of attributes that define ways to make web content and web applications (especially those developed with JavaScript) more accessible to people with disabilities.


    Accessible Rich Internet Applications (ARIA) is a set of attributes that define ways to make web content and web applications (especially those developed with JavaScript) more accessible to people with disabilities.

    It supplements HTML so that interactions and widgets commonly used in applications can be passed to assistive technologies when there is not otherwise a mechanism. For example, ARIA enables accessible JavaScript widgets, form hints and error messages, live content updates, and more.

    Warning: Many of these widgets were later incorporated into HTML5, and developers should prefer using the correct semantic HTML element over using ARIA, if such an element exists. For instance, native elements have built-in , roles and states. However, if you choose to use ARIA, you are responsible for mimicking the equivalent browser behavior in script.

    use is "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so."

    Note: There is a saying "No ARIA is better than bad ARIA." In , they found that Home pages with ARIA present averaged 41% more detected errors than those without ARIA. While ARIA is designed to make web pages more accessible, if used incorrectly, it can do more harm than good.

    Here's the markup for a progress bar widget:

    This progress bar is built using a , which has no meaning. We include ARIA roles and properties to add meaning. In this example, the attribute informs the browser that this element is actually a JavaScript-powered progress bar widget. The and attributes specify the minimum and maximum values for the progress bar, and the describes the current state of it and therefore must be kept updated with JavaScript.

    Along with placing them directly in the markup, ARIA attributes can be added to the element and updated dynamically using JavaScript code like this:

    All content that is available to non-assistive technology users must be made available to assistive technologies. Similarly, no features should be included targeting assistive technology users that aren't also accessible to those not using assistive technologies. The above progressbar needs to be styled to make it look like a progressbar.

    It would have been much simpler to use the native element instead:

    Note: The min attribute is not allowed for the element; its minimum value is always 0.

    Note: HTML landmark elements (, , etc.) have built-in implicit ARIA roles, so there is no need to duplicate them.

    Like any other web technology, there are varying degrees of support for ARIA. Support is based on the operating system and browser being used, as well as the kind of assistive technology interfacing with it. In addition, the version of the operating system, browser, and assistive technology are contributing factors. Older software versions may not support certain ARIA roles, have only partial support, or misreport its functionality.

    It is also important to acknowledge that some people who rely on assistive technology are reluctant to upgrade their software, for fear of losing the ability to interact with their computer and browser. Because of this, it is important to whenever possible, as semantic HTML has far better support for assistive technology.

    It is also important to test your authored ARIA with actual assistive technology. Much as how browser emulators and simulators are not an effective solution for testing full support, proxy assistive technology solutions aren't sufficient to fully guarantee functionality.

    ByLabelText

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByLabelText, queryByLabelText, getAllByLabelText, queryAllByLabelText, findByLabelText, findAllByLabelText

    hashtag
    API

    This will search for the label that matches the given TextMatch

    React Testing Library

    hashtag
    About Queries | Testing Library

    Excerpt

    Overview


    Aria

    hashtag
    WAI-ARIA Authoring Practices 1.1

    Excerpt

    This document provides readers with an understanding of how to use WAI-ARIA 1.1 [WAI-ARIA] to create accessible rich internet applications. It describes considerations that might not be evident to most authors from the WAI-ARIA specification alone and recommends approaches to make widgets, navigation, and behaviors accessible using WAI-ARIA roles, states, and properties. This document is directed primarily to Web application developers, but the guidance is also useful for user agent and assistive technology developers.


    React Testing Library (events)

    hashtag
    Firing Events | Testing Library

    Excerpt

    Note


    React

    hashtag

    Although this tutorial doesn't use this approach, many React applications define their styles on a per-component basis, rather than in a single, monolithic stylesheet.

    create-react-app makes it possible to import CSS files into JavaScript modules, so that CSS is only sent to your user when the corresponding component is rendered. For this app, we could have for example written a dedicated Form.css file to house the styles of those respective components, then imported the styles into their respective modules like this:

    React interactivity:

    Editing, filtering, conditional rendering

    As we near the end of our React journey (for now at least), we'll add the finishing touches to the main areas of functionality in our Todo list app. This includes allowing you to edit existing tasks, and filtering the list of tasks between all, completed, and incomplete tasks. We'll look at conditional UI rendering along the way.

    hashtag

    We don't have a user interface for editing the name of a task yet. We'll get to that in a moment. To start with, we can at least implement an editTask() function in App.js

    {
      componentName: 'ContentMain',
      content: [{
          uid: '',
          componentName: 'Single Step Form',
          dataSource: '',
          fields: {
              ModelJson: {
                  value: '[ { "title":"Form Name", "fields":{ "Name":{ "label":"Form Name", "type":"input", "value":"Form Name", "name":"Name" }, "Id":{ "label":"Id", "type":"hidden", "value":"", "name":"Id" } }, "fresh":true }, { "title":"Text Input", "fields":{ "FormId":{ "label":"FormId", "type":"hidden", "value":"stepOne", "name":"FormId" }, "Name":{ "label":"Name", "type":"hidden", "value":"textinput", "name":"Name" }, "Id":{ "label":"Id", "type":"input", "value":"fixtureName", "name":"Id" }, "Label":{ "label":"Label", "type":"input", "value":"Fixture Selection", "name":"Label" }, "BackEndLabel":{ "label":"BackEnd Label", "type":"input", "value":"Fixture Selection", "name":"BackEndLabel" }, "Value":{ "label":"Default Value", "type":"input", "value":"", "name":"Value" }, "DefaultValueSource":{ "label":"Default Value Source", "type":"select", "value":[ { "value":"None", "selected":false, "label":"None" }, { "value":"cookie", "selected":true, "label":"Cookie" } ], "name":"DefaultValueSource" }, "DefaultValueKey":{ "label":"Default Value Source Key", "type":"input", "value":"fixtureName", "name":"DefaultValueKey"}]
              }
          }
      }]
    }
    - `createdFields` is memoized to cache the original value and keep it from re-rendering each cycle
    - The `false` parameter signifies that its not to return a multidimensional array, just a single array
    
    5. `createFormInit()` is a 'factory' for both `<SingleStepForm />` and `<MultiStepForm />`. We will focus on what happens if this is called from `<SingleStepForm />`.
    
    ```typescript
    
    const createFormInit: {
      (arr: Array<ParsedFormModel>, multi: true): CFReturnType[][];
      (arr: Array<ParsedFormModel>, multi: false): CFReturnType[];
    } = (arr: Array<ParsedFormModel>, multi: boolean): any => {
      return multi ? multiStepFormFields(arr) : parseFields(arr);
    };
    const parseFields = (arr: Array<ParsedFormModel>) => {
    
    const items = arr.reduce((acc: Array<CFReturnType | null>, curr) => {
    
    const formField = createForm(curr);
        return [...acc, formField];
      }, []);
    
    // filter out any null values due to early returns from hidden fields
      return items.filter(Boolean) as Array<CFReturnType>;
    };
    - `dataMap`
    
      - `dataMap` is an object with `inputMap` values as the key and related props as the value. All of these will contain a 'file' value. This is the name of the React component that will be dynamically imported, and some of them will contain a 'props' value. Props is an object that contains more details for that particular input type such as type, icon, masking function etc..
    
        ```javascript
    
        ```
    
    const dataMap: CFMappingType["dataMap"] = {
    
    // ...
    input: {
    file: "Input",
    props: { type: "text" },
    },
    phone: {
    file: "Input",
    props: {
    type: "tel",
    icon: "phone",
    mask: masks.tel,
    },
    },
    radio: {
    file: "RadioGroup",
    },
    
    // ...
    };
    2. Inside `createForm()` it will take the incoming field name and use it to index `inputMap`. At this point one of three things will happen:
       - It will find a match and return that input type
       - It will return a null value (these are currently hidden fields or unimportant fields)
       - It will return undefined\
         \
         If it returns a type we will continue, if it returns a null we return a null out of createForm(), if its undefined we then assume and assign this field with a type 'input'
    3. Now that we have the field type (the `inputType` value), we use this to index `dataMap` and return the file and or props from that
    
       - We take this file name and dynamically import that component (ie: 'Input', 'Heading', 'RadioGroup', 'Tabs', etc..)
    
       ```javascript
    4. We then now build our return object which gets added to the array in `parseFields()` and gets sent back to `<SingleStepForm />`
    
    ```javascript
    return {
      Component,
      data: getData(fields, title),
      file,
      formName: fields?.FormId?.value,
      id: uid(32),
      props: {
        columns: getColumnWidth(fields.ColumnWidth?.value),
        ...props,
      },
      validations: getValidations(file, fields, props?.type),
    };
    const getData: GetDataProps = ({ fields, title }) => ({
      customValidationErrorMsg:
        fields?.CustomValidationErrorMsg?.value || "field is required",
      items: parseItems(fields?.InputItems?.value),
      label: fields?.Label?.value || "",
      maxLength: parseInt(fields?.MaximumLength?.value) || 524288,
      minLength: parseInt(fields?.MinimumLength?.value) || 0,
      name: fields?.Name?.value,
      placeholder: fields?.PlaceholderText?.value,
      required: fields?.Required?.value || false,
      tabs: fields?.Tabs?.value.split("\n") || [],
      title,
      toolTipText: fields?.TooltipText?.value,
    });
    const getValidations: GetValidationProps = (file, fields, regex = "") => {
      const skipValidation = ["Heading", "Recaptcha", "Tabs"];
      let pattern;
    
      if (file && skipValidation.includes(file)) return null;
    
      // Validations will usually come through as a string value from sitecore
    
      // but can also come through as an array of objects, these have the type 'select'
    
      // We first need to parse through this array and grab the value of the selected validation pattern
      if (fields?.ValidationPattern?.type === "select") {
        pattern = getSelectedValue(fields.ValidationPattern.value);
      } else {
        pattern = fields?.ValidationPattern?.value;
      }
    
      return {
        shouldConfirm: fields?.AppearsOnFormConfirmation?.value,
        validationPattern:
          // 1. by regex in mapping props (phone, ssn)
    
          // 2. by named regex pattern coming from Sitecore
          regexMap[regex] || regexMap[pattern],
      };
    };
       return (
         <div className="relative">
           <InputText {...propData} />
           <div className="hidden lg:block absolute top-0 right-0 mt-4 lg:-mr-48">
             <Tooltip error={Boolean(errors[name])} message={toolTipText} />
           </div>
         </div>
       );
     };
     ```
    [[form stepper element], [...first section fields], [...second section fields] ...etc]
    const formModel: Array<ParsedFormModel> = JSON.parse(modelJson.value);
    
    const createdFields = useMemo(() => createFormInit(formModel, true), []);
    
    const createdFieldsWithoutTabs = [...createdFields.slice(1), []];
    
    const createdFieldsOnlyTabFields = [
      ...createdFields[0][0].data.tabs,
      "Confirmation",
    ];
    
    // ... return
    <FormComponent onSubmit={handleFormSubmit}>
      <FormStepper activeIndex={activeIndex} content={createdFieldsOnlyTabFields} />
      {createdFieldsWithoutTabs.map((innerArray, index) =>
        innerArray.map(
          ({ Component, data, id, props, validations, ...rest }: CFReturnType) => {
            return (
              <FieldWrapper
                className={index === activeIndex ? "block" : "hidden"}
                columns={props?.columns}
                key={id}
              >
                <Component
                  register={register({
                    pattern: regexPattern(validations),
                    required: isRequired(data),
                    validate: {
                      match: (value: string) =>
                        matchingEmails(
                          name.toLowerCase(),
                          value,
                          getValues(["email", "emailconf"])
                        ),
                    },
                  })}
                  {...{ data, errors, name, props, ...rest }}
                />
              </FieldWrapper>
            );
          }
        )
      )}
      {isLastStep && <ConfirmationStep data={confirmedValues} {...{ fields }} />}
      <ButtonWrapper />
    </FormComponent>;
    [
      {
        "title": "Radio List",
        "fields": {
          "FormId": {
            "label": "FormId",
            "type": "hidden",
            "value": "stepOne",
            "name": "FormId"
          },
          "Name": {
            "label": "Name",
            "type": "input",
            "value": "Are you a customer requesting a single light for your yard or property?",
            "name": "Name"
          },
          "Label": {
            "label": "Label",
            "type": "input",
            "value": "Are you a customer requesting a single light for your yard or property?",
            "name": "Label"
          },
          "BackEndLabel": {
            "label": "BackEnd Label",
            "type": "input",
            "value": "Are you a customer requesting a single light for your yard or property?",
            "name": "BackEndLabel"
          },
          "GroupName": {
            "label": "Group Name",
            "type": "input",
            "value": "GroupName",
            "name": "GroupName"
          },
          "Value": {
            "label": "Default Value",
            "type": "input",
            "value": "",
            "name": "Value"
          },
          "Id": {
            "label": "Id",
            "type": "input",
            "value": "radiolist",
            "name": "Id"
          },
          "TooltipText": {
            "label": "Tooltip Text",
            "type": "input",
            "value": "",
            "name": "TooltipText"
          },
          "CustomValidationErrorMsg": {
            "label": "Custom Validation Err. Msg.",
            "type": "input",
            "value": "Please pick an option.",
            "name": "CustomValidationErrorMsg"
          },
          "AppearsOnFormConfirmation": {
            "label": "Appear on Confirmation",
            "type": "checkbox",
            "value": true,
            "name": "AppearsOnFormConfirmation"
          },
          "Required": {
            "label": "Required",
            "type": "checkbox",
            "value": true,
            "name": "Required"
          },
          "InputItems": {
            "label": "Items",
            "type": "radiolist",
            "value": "[{\"text\":\"Yes\",\"value\":\"Yes\"},{\"text\":\"No\",\"value\":\"No\"}]",
            "name": "InputItems"
          }
        },
        "fresh": true
      },
      {
        "title": "Section Header",
        "fields": {
          "FormId": {
            "label": "FormId",
            "type": "hidden",
            "value": "stepOne",
            "name": "FormId"
          },
          "Name": {
            "label": "Name",
            "type": "hidden",
            "value": "sectionhdr",
            "name": "Name"
          },
          "Label": {
            "label": "Label",
            "type": "input",
            "value": "Name & Address",
            "name": "Label"
          },
          "BackEndLabel": {
            "label": "BackEnd Label",
            "type": "input",
            "value": "Name & Address",
            "name": "BackEndLabel"
          },
          "Id": {
            "label": "Id",
            "type": "hidden",
            "value": "sectionhdr",
            "name": "Id"
          }
        },
        "fresh": true
      },
      {
        "title": "First Name",
        "fields": {
          "FormId": {
            "label": "FormId",
            "type": "hidden",
            "value": "stepOne",
            "name": "FormId"
          },
          "Name": {
            "label": "Name",
            "type": "hidden",
            "value": "FirstName",
            "name": "Name"
          },
          "Id": {
            "label": "Id",
            "type": "hidden",
            "value": "FirstName",
            "name": "Id"
          },
          "Label": {
            "label": "Label",
            "type": "hidden",
            "value": "First Name",
            "name": "Label"
          },
          "BackEndLabel": {
            "label": "BackEnd Label",
            "type": "hidden",
            "value": "First Name",
            "name": "BackEndLabel"
          },
          "Value": {
            "label": "Default Value",
            "type": "hidden",
            "value": "",
            "name": "Value"
          },
          "ValidationPattern": {
            "label": "Validation Rule",
            "type": "hidden",
            "value": "lettersWhiteSpace",
            "name": "ValidationPattern"
          },
          "TooltipText": {
            "label": "Tooltip Text",
            "type": "input",
            "value": "",
            "name": "TooltipText"
          },
          "CustomValidationErrorMsg": {
            "label": "Custom Validation Err. Msg.",
            "type": "hidden",
            "value": "Please enter a first name.",
            "name": "CustomValidationErrorMsg"
          },
          "AppearsOnFormConfirmation": {
            "label": "Appear on Confirmation",
            "type": "checkbox",
            "value": false,
            "name": "AppearsOnFormConfirmation"
          },
          "Required": {
            "label": "Required",
            "type": "checkbox",
            "value": true,
            "name": "Required"
          },
          "MinimumLength": {
            "label": "Min. Length",
            "type": "hidden",
            "value": "",
            "name": "MinimumLength"
          },
          "MaximumLength": {
            "label": "Max. Length",
            "type": "hidden",
            "value": "40",
            "name": "MaximumLength"
          },
          "Predefined": {
            "label": "Predefined",
            "type": "hidden",
            "value": "true",
            "name": "Predefined"
          },
          "ColumnWidth": {
            "label": "Column Width",
            "type": "select",
            "value": [
              {
                "value": "2",
                "selected": false,
                "label": "2"
              },
              {
                "value": "3",
                "selected": true,
                "label": "3"
              },
              {
                "value": "4",
                "selected": false,
                "label": "4"
              },
              {
                "value": "5",
                "selected": false,
                "label": "5"
              },
              {
                "value": "6",
                "selected": false,
                "label": "6"
              }
            ],
            "name": "ColumnWidth"
          }
        },
        "fresh": true
      }
    ]
    <FormComponent onSubmit={handleSubmit(onSubmit)}>
      {createdFields.map(
        ({
          Component,
          data,
          id,
          props,
          validations,
          ...rest
        }: CFReturnType) => {
          return (
            <FieldWrapper columns={props?.columns} key={id}>
              <Component
                register={register({
                  pattern: regexPattern(validations),
                  required: isRequired(data),
                  validate: {
                    match: (value: string) =>
                      matchingEmails(
                        name.toLowerCase(),
                        value,
                        getValues(["email", "emailconf"])
                      ),
                  },
                })}
                {...{ data, errors, name, props, ...rest }}
              />
            </FieldWrapper>
          );
        }
      )}
      <Button type="submit" variant="primary">
        submit
      </Button>
    </FormComponent>
    Pick<ComponentTypes, "text">;
    
    // only use 'text' type
    Partial<Pick<ComponentTypes, "text">>;
    
    // only use 'text' type
    
    // the text type is optional
    Required<Pick<ComponentTypes, "text">>;
    
    // only use 'text' type
    
    // the text type is required
    {['class', 'enum', 'interface', 'namespace', 'type', 'variable-and-function'].map(item => (
    {item.split('-').join(' ')}
    ))}
    class foo {}
    class Foo {}
    class Foo {
      Bar: number;
      BazQux() {}
    }
    class Foo {
      bar: number;
      bazQux() {}
    }
    enum backgroundColor {}
    enum BackgroundColor {}
    interface checkboxProps {}
    interface CheckboxProps {}
    interface CheckboxProps {
      IsSelected: boolean;
    }
    interface CheckboxProps = {
      isSelected: boolean;
    }
    namespace foo {}
    namespace Foo {}
    type imageProps = { src: string; alt: string };
    type ImageProps = { src: string; alt: string };
    const FooBar = "baz";
    
    const FooBar = () => "baz";
    const fooBar = "baz";
    
    const fooBar = () => "baz";
    <div>
      <label for="cc-number">Enter your credit card number</label>
      <input name="cc-number" id="cc-number" autocomplete="off">
    </div>
    import React from "react";
    import { rest } from "msw";
    import { setupServer } from "msw/node";
    import { render, fireEvent, waitFor, screen } from "@testing-library/react";
    import "@testing-library/jest-dom";
    import Fetch from "../fetch";
    
    const server = setupServer(
      rest.get("/greeting", (req, res, ctx) => {
        return res(ctx.json({ greeting: "hello there" }));
      })
    );
    
    beforeAll(() => server.listen());
    afterEach(() => server.resetHandlers());
    afterAll(() => server.close());
    
    test("loads and displays greeting", async () => {
      render(<Fetch url="/greeting" />);
    
      fireEvent.click(screen.getByText("Load Greeting"));
    
      await waitFor(() => screen.getByRole("heading"));
    
      expect(screen.getByRole("heading")).toHaveTextContent("hello there");
      expect(screen.getByRole("button")).toBeDisabled();
    });
    
    test("handles server error", async () => {
      server.use(
        rest.get("/greeting", (req, res, ctx) => {
          return res(ctx.status(500));
        })
      );
    
      render(<Fetch url="/greeting" />);
    
      fireEvent.click(screen.getByText("Load Greeting"));
    
      await waitFor(() => screen.getByRole("alert"));
    
      expect(screen.getByRole("alert")).toHaveTextContent("Oops, failed to fetch!");
      expect(screen.getByRole("button")).not.toBeDisabled();
    });

    validate: custom function that passes the field's value through a method that returns a boolean

    "
    width
    "
    ]
    ?:
    string
    ;
    Type 'Function' provides no match for the signature 'new (...args: any): any'. type T4 = neverTry
    submitarrow-up-right
    <form>arrow-up-right
    HTML formsarrow-up-right
    global attributesarrow-up-right

    SCR27: Reordering page sections using the Document Object Modelarrow-up-right

    PDF3: Ensuring correct tab and reading order in PDF documentsarrow-up-right
    SL34: Using the Silverlight Default Tab Sequence and Altering Tab Sequences With Propertiesarrow-up-right
    SCR26: Inserting dynamic content into the Document Object Model immediately following its trigger elementarrow-up-right
    SCR37: Creating Custom Dialogs in a Device Independent Wayarrow-up-right

    Yes

    queryBy

    null

    return

    throw

    No

    getAllBy

    throw

    array

    array

    No

    findAllBy

    throw

    array

    array

    Yes

    queryAllBy

    []

    array

    array

    No

    getAllByLabelText

  • queryAllByLabelText

  • findByLabelText

  • findAllByLabelText

  • ByPlaceholderText find by input placeholder value

    • getByPlaceholderText

    • queryByPlaceholderText

    • getAllByPlaceholderText

    • queryAllByPlaceholderText

    • findByPlaceholderText

    • findAllByPlaceholderText

  • ByText find by element text content

    • getByText

    • queryByText

    • getAllByText

    • queryAllByText

    • findByText

    • findAllByText

  • ByDisplayValue find by form element current value

    • getByDisplayValue

    • queryByDisplayValue

    • getAllByDisplayValue

    • queryAllByDisplayValue

    • findByDisplayValue

    • findAllByDisplayValue

  • ByAltText find by img alt attribute

    • getByAltText

    • queryByAltText

    • getAllByAltText

    • queryAllByAltText

    • findByAltText

    • findAllByAltText

  • ByTitle find by title attribute or svg title tag

    • getByTitle

    • queryByTitle

    • getAllByTitle

    • queryAllByTitle

    • findByTitle

    • findAllByTitle

  • ByRole find by aria role

    • getByRole

    • queryByRole

    • getAllByRole

    • queryAllByRole

    • findByRole

    • findAllByRole

  • ByTestId find by data-testid attribute

    • getByTestId

    • queryByTestId

    • getAllByTestId

    • queryAllByTestId

    • findByTestId

    • findAllByTestId

  • fireEvent.click(node)
  • See all supported eventsarrow-up-right

  • act wrapper around react-dom/test-utils actarrow-up-right; React Testing Library wraps render and fireEvent in a call to act already so most cases should not require using it manually

  • cleanup clears the DOM (use with afterEach to reset DOM between tests)

    getBy

    throw

    return

    throw

    No

    findBy

    throw

    return

    Get the printable cheat sheetarrow-up-right

    throw

    Custom matchers
    • toBeDisabled

    • toBeEnabled

    • toBeEmptyDOMElement

    • toBeInTheDocument

    • toBeInvalid

    • toBeRequired

    • toBeValid

    • toBeVisible

    • toContainElement

    • toContainHTML

    • toHaveAccessibleDescription

    • toHaveAccessibleName

    • toHaveAttribute

    • toHaveClass

    • toHaveFocus

    • toHaveFormValues

    • toHaveStyle

    • toHaveTextContent

    • toHaveValue

    • toHaveDisplayValue

    • toBeChecked

    • toBePartiallyChecked

    • toHaveErrorMessage

  • Deprecated matchers

    • toBeEmpty

    • toBeInTheDOM

    • toHaveDescription

  • Inspiration

  • Other Solutions

  • Guiding Principles

  • Contributors

  • LICENSE

  • set to either
    hidden
    or
    collapse
  • it does not have its css property opacity set to 0

  • its parent element is also visible (and so on up to the top of the DOM tree)

  • it does not have the hiddenarrow-up-right attribute

  • if <details /> it has the open attribute

  • if there's a single one with the given name attribute, it is treated as a boolean, returning true if the checkbox is checked, false if unchecked.
  • if there's more than one checkbox with the same name attribute, they are all treated collectively as a single form control, which returns the value as an array containing all the values of the selected checkboxes in the collection.

  • <input type="radio"> elements are all grouped by the name attribute, and such a group treated as a single form control. This form control returns the value as a string corresponding to the value attribute of the selected radio button within the group.

  • <input type="text"> elements return the value as a string. This also applies to <input> elements having any other possible type attribute that's not explicitly covered in different rules above (e.g. search, email, date, password, hidden, etc.)

  • <select> elements without the multiple attribute return the value as a string corresponding to the value attribute of the selected option, or undefined if there's no selected option.

  • <select multiple> elements return the value as an array containing all the values of the selected optionsarrow-up-right.

  • <textarea> elements return their value as a string. The value corresponds to their node content.

  • yarnarrow-up-right
    eslint-plugin-jest-domarrow-up-right
    tests setup filearrow-up-right
    disabledarrow-up-right
    #144arrow-up-right
    #144arrow-up-right
    aria-invalid attributearrow-up-right
    checkValidity()arrow-up-right
    aria-invalid attributearrow-up-right
    checkValidity()arrow-up-right
    accessible descriptionarrow-up-right
    expect.stringContainingarrow-up-right
    expect.stringMatchingarrow-up-right
    accessible namearrow-up-right
    expect.stringContainingarrow-up-right
    expect.stringMatchingarrow-up-right
    expect.stringContainingarrow-up-right
    expect.stringMatchingarrow-up-right
    formarrow-up-right
    fieldsetarrow-up-right
    .elementsarrow-up-right
    ARIA error messagearrow-up-right
    aria-errormessage specarrow-up-right
    herearrow-up-right
    jest's built-in matchersarrow-up-right
    aria-describedby attributearrow-up-right
    join the referenced elements’ text content separated by a spacearrow-up-right
    jest-axearrow-up-right
    <span title="Delete" id="2"></span>
    <svg>
      <title>Close</title>
      <g><path /></g>
    </svg>
    import { screen } from "@testing-library/dom";
    
    const deleteElement = screen.getByTitle("Delete");
    const closeElement = screen.getByTitle("Close");
    @testing-library/jest-domarrow-up-right
    @testing-library/user-eventarrow-up-right
    implementation detailsarrow-up-right
    Avoid Nesting When You're Testingarrow-up-right
    CodeSandboxarrow-up-right
    testing-playground.comarrow-up-right
    Testing Playgroundarrow-up-right
    Kent C. Doddsarrow-up-right
    Jest's mocking featurearrow-up-right

    a parenthesized constant enum expression

  • one of the +, -, ~ unary operators applied to constant enum expression

  • +, -, *, /, %, <<, >>, >>>, &, |, ^ binary operators with constant enum expressions as operands It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity .

  • a unary minus applied to any numeric literal (e.g. -1, -100)

  • [importsNotUsedAsValues: "preserve"](https: //www.typescriptlang.org/tsconfig#importsNotUsedAsValues) will not elide imports for const enums used as values, but ambient const enums do not guarantee that runtime .js files exist. The unresolvable imports cause errors at runtime. The usual way to unambiguously elide imports, [type-only imports](https: //www.typescriptlang.org/docs/handbook/modules.html#importing-types), [does not allow const enum values](https: //github.com/microsoft/TypeScript/issues/40344), currently.

  • keyboard accessibilityarrow-up-right
    The first rule of ARIAarrow-up-right
    WebAim's survey of over one million home pagesarrow-up-right
    <div>arrow-up-right
    role="progressbar"arrow-up-right
    aria-valueminarrow-up-right
    aria-valuemaxarrow-up-right
    aria-valuenowarrow-up-right
    <progress>arrow-up-right
    <progress>arrow-up-right
    <main>arrow-up-right
    <header>arrow-up-right
    <nav>arrow-up-right
    use semantic HTML elementsarrow-up-right
    Responsive Web Accessibility Review of Duke Energy Manual Audit for Duke Energychevron-right
    , then find the element associated with that label.

    The example below will find the input node for the following DOM structures:

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    hashtag
    Options

    hashtag
    name

    The example above does NOT find the input node for label text broken up by elements. You can use getByRole('textbox', { name: 'Username' }) instead which is robust against switching to aria-label or aria-labelledby.

    hashtag
    selector

    If it is important that you query an actual <label> element you can provide a selector in the options:

    Note

    getByLabelText will not work in the case where a for attribute on a <label> element matches an id attribute on a non-form element.

    Note

    Most projects have a few use cases for fireEvent, but the majority of the time you should probably use [@testing-library/user-event](https: //testing-library.com/docs/ecosystem-user-event).

    hashtag
    fireEvent

    Fire DOM events.

    hashtag
    fireEvent[eventName]

    Convenience methods for firing DOM events. Check out [src/event-map.js](https: //github.com/testing-library/dom-testing-library/blob/master/src/event-map.js) for a full list as well as default eventProperties.

    target: When an event is dispatched on an element, the event has the subjected element on a property called target. As a convenience, if you provide a target property in the eventProperties (second argument), then those properties will be assigned to the node which is receiving the event.

    This is particularly useful for a change event:

    dataTransfer: Drag events have a dataTransfer property that contains data transferred during the operation. As a convenience, if you provide a dataTransfer property in the eventProperties (second argument), then those properties will be added to the event.

    This should predominantly be used for testing drag and drop interactions.

    Keyboard events: There are three event types related to keyboard input - keyPress, keyDown, and keyUp. When firing these you need to reference an element in the DOM and the key you want to fire.

    You can find out which key code to use at [https: //keycode.info/](https: //keycode.info).

    hashtag
    createEvent[eventName]

    Convenience methods for creating DOM events that can then be fired by fireEvent, allowing you to have a reference to the event created: this might be useful if you need to access event properties that cannot be initiated programmatically (such as timeStamp).

    You can also create generic events:

    hashtag
    Using Jest Function Mocks

    [Jest's Mock functions](https: //jestjs.io/docs/en/mock-functions) can be used to test that a callback passed to the function was called, or what it was called when the event that should trigger the callback function does trigger the bound callback.

    • React

    import { render, fireEvent, screen } from "@testing-library/react";
    
    test("loads items eventually", async () => {
      render(<Page />);
    
      // Click button
      fireEvent.click(screen.getByText("Load"));
    
      // Wait for page to update with query text
      const items = await screen.findAllByText(/Item #[0-9]: /);
      expect(items).toHaveLength(10);
    });
    <div>Hello World</div>
    // Matching a string:
    getByText("Hello World"); // full string match
    getByText("llo Worl", { exact: false }); // substring match
    getByText("hello world", { exact: false }); // ignore case
    
    // Matching a regex:
    getByText(/World/); // substring match
    getByText(/world/i); // substring match, ignore case
    getByText(/^hello world$/i); // full string match, ignore case
    getByText(/Hello W?oRlD/i); // advanced regex
    
    // Matching with a custom function:
    getByText((content, element) => content.startsWith("Hello"));
    // import dependencies
    import React from "react";
    
    // import API mocking utilities from Mock Service Worker
    import { rest } from "msw";
    import { setupServer } from "msw/node";
    
    // import react-testing methods
    import { render, fireEvent, waitFor, screen } from "@testing-library/react";
    
    // add custom jest matchers from jest-dom
    import "@testing-library/jest-dom";
    // the component to test
    import Fetch from "../fetch";
    test("loads and displays greeting", async () => {
      // Arrange
      // Act
      // Assert
    });
    // declare which API requests to mock
    const server = setupServer(
      // capture "GET /greeting" requests
      rest.get("/greeting", (req, res, ctx) => {
        // respond using a mocked JSON body
        return res(ctx.json({ greeting: "hello there" }));
      })
    );
    
    // establish API mocking before all tests
    beforeAll(() => server.listen());
    // reset any request handlers that are declared as a part of our tests
    // (i.e. for testing one-time error scenarios)
    afterEach(() => server.resetHandlers());
    // clean up once the tests are done
    afterAll(() => server.close());
    
    // ...
    
    test("handles server error", async () => {
      server.use(
        // override the initial "GET /greeting" request handler
        // to return a 500 Server Error
        rest.get("/greeting", (req, res, ctx) => {
          return res(ctx.status(500));
        })
      );
    
      // ...
    });
    render(<Fetch url="/greeting" />);
    fireEvent.click(screen.getByText("Load Greeting"));
    
    // wait until the `get` request promise resolves and
    // the component calls setState and re-renders.
    // `waitFor` waits until the callback doesn't throw an error
    
    await waitFor(() =>
      // getByRole throws an error if it cannot find an element
      screen.getByRole("heading")
    );
    // assert that the alert message is correct using
    // toHaveTextContent, a custom matcher from jest-dom.
    expect(screen.getByRole("alert")).toHaveTextContent("Oops, failed to fetch!");
    
    // assert that the button is not disabled using
    // toBeDisabled, a custom matcher from jest-dom.
    expect(screen.getByRole("button")).not.toBeDisabled();
    import React, { useState, useReducer } from "react";
    import axios from "axios";
    
    const initialState = {
      error: null,
      greeting: null,
    };
    
    function greetingReducer(state, action) {
      switch (action.type) {
        case "SUCCESS": {
          return {
            error: null,
            greeting: action.greeting,
          };
        }
        case "ERROR": {
          return {
            error: action.error,
            greeting: null,
          };
        }
        default: {
          return state;
        }
      }
    }
    
    export default function Fetch({ url }) {
      const [{ error, greeting }, dispatch] = useReducer(
        greetingReducer,
        initialState
      );
      const [buttonClicked, setButtonClicked] = useState(false);
    
      const fetchGreeting = async (url) =>
        axios
          .get(url)
          .then((response) => {
            const { data } = response;
            const { greeting } = data;
            dispatch({ type: "SUCCESS", greeting });
            setButtonClicked(true);
          })
          .catch((error) => {
            dispatch({ type: "ERROR", error });
          });
    
      const buttonText = buttonClicked ? "Ok" : "Load Greeting";
    
      return (
        <div>
          <button onClick={() => fetchGreeting(url)} disabled={buttonClicked}>
            {buttonText}
          </button>
          {greeting && <h1>{greeting}</h1>}
          {error && <p role="alert">Oops, failed to fetch!</p>}
        </div>
      );
    }
    npm install --save-dev @testing-library/jest-dom
    yarn add --dev @testing-library/jest-dom
    // In your own jest-setup.js (or any other name)
    import "@testing-library/jest-dom";
    
    // In jest.config.js add (if you haven't already)
    setupFilesAfterEnv: ["<rootDir>/jest-setup.js"];
      // In tsconfig.json
      "include": [
        ...
        "./jest-setup.ts"
      ],
    toBeDisabled();
    <button data-testid="button" type="submit" disabled>submit</button>
    <fieldset disabled><input type="text" data-testid="input" /></fieldset>
    <a href="..." disabled>link</a>
    expect(getByTestId("button")).toBeDisabled();
    expect(getByTestId("input")).toBeDisabled();
    expect(getByText("link")).not.toBeDisabled();
    toBeEnabled();
    toBeEmptyDOMElement();
    <span data-testid="not-empty"><span data-testid="empty"></span></span>
    <span data-testid="with-whitespace"> </span>
    <span data-testid="with-comment"><!-- comment --></span>
    expect(getByTestId("empty")).toBeEmptyDOMElement();
    expect(getByTestId("not-empty")).not.toBeEmptyDOMElement();
    expect(getByTestId("with-whitespace")).not.toBeEmptyDOMElement();
    toBeInTheDocument();
    <span data-testid="html-element"><span>Html Element</span></span>
    <svg data-testid="svg-element"></svg>
    expect(
      getByTestId(document.documentElement, "html-element")
    ).toBeInTheDocument();
    expect(
      getByTestId(document.documentElement, "svg-element")
    ).toBeInTheDocument();
    expect(
      queryByTestId(document.documentElement, "does-not-exist")
    ).not.toBeInTheDocument();
    toBeInvalid();
    <input data-testid="no-aria-invalid" />
    <input data-testid="aria-invalid" aria-invalid />
    <input data-testid="aria-invalid-value" aria-invalid="true" />
    <input data-testid="aria-invalid-false" aria-invalid="false" />
    
    <form data-testid="valid-form">
      <input />
    </form>
    
    <form data-testid="invalid-form">
      <input required />
    </form>
    expect(getByTestId("no-aria-invalid")).not.toBeInvalid();
    expect(getByTestId("aria-invalid")).toBeInvalid();
    expect(getByTestId("aria-invalid-value")).toBeInvalid();
    expect(getByTestId("aria-invalid-false")).not.toBeInvalid();
    
    expect(getByTestId("valid-form")).not.toBeInvalid();
    expect(getByTestId("invalid-form")).toBeInvalid();
    toBeRequired();
    <input data-testid="required-input" required />
    <input data-testid="aria-required-input" aria-required="true" />
    <input data-testid="conflicted-input" required aria-required="false" />
    <input data-testid="aria-not-required-input" aria-required="false" />
    <input data-testid="optional-input" />
    <input data-testid="unsupported-type" type="image" required />
    <select data-testid="select" required></select>
    <textarea data-testid="textarea" required></textarea>
    <div data-testid="supported-role" role="tree" required></div>
    <div data-testid="supported-role-aria" role="tree" aria-required="true"></div>
    expect(getByTestId("required-input")).toBeRequired();
    expect(getByTestId("aria-required-input")).toBeRequired();
    expect(getByTestId("conflicted-input")).toBeRequired();
    expect(getByTestId("aria-not-required-input")).not.toBeRequired();
    expect(getByTestId("optional-input")).not.toBeRequired();
    expect(getByTestId("unsupported-type")).not.toBeRequired();
    expect(getByTestId("select")).toBeRequired();
    expect(getByTestId("textarea")).toBeRequired();
    expect(getByTestId("supported-role")).not.toBeRequired();
    expect(getByTestId("supported-role-aria")).toBeRequired();
    toBeValid();
    <input data-testid="no-aria-invalid" />
    <input data-testid="aria-invalid" aria-invalid />
    <input data-testid="aria-invalid-value" aria-invalid="true" />
    <input data-testid="aria-invalid-false" aria-invalid="false" />
    
    <form data-testid="valid-form">
      <input />
    </form>
    
    <form data-testid="invalid-form">
      <input required />
    </form>
    expect(getByTestId("no-aria-invalid")).toBeValid();
    expect(getByTestId("aria-invalid")).not.toBeValid();
    expect(getByTestId("aria-invalid-value")).not.toBeValid();
    expect(getByTestId("aria-invalid-false")).toBeValid();
    
    expect(getByTestId("valid-form")).toBeValid();
    expect(getByTestId("invalid-form")).not.toBeValid();
    toBeVisible();
    <div data-testid="zero-opacity" style="opacity: 0">Zero Opacity Example</div>
    <div data-testid="visibility-hidden" style="visibility: hidden">
      Visibility Hidden Example
    </div>
    <div data-testid="display-none" style="display: none">Display None Example</div>
    <div style="opacity: 0">
      <span data-testid="hidden-parent">Hidden Parent Example</span>
    </div>
    <div data-testid="visible">Visible Example</div>
    <div data-testid="hidden-attribute" hidden>Hidden Attribute Example</div>
    expect(getByText("Zero Opacity Example")).not.toBeVisible();
    expect(getByText("Visibility Hidden Example")).not.toBeVisible();
    expect(getByText("Display None Example")).not.toBeVisible();
    expect(getByText("Hidden Parent Example")).not.toBeVisible();
    expect(getByText("Visible Example")).toBeVisible();
    expect(getByText("Hidden Attribute Example")).not.toBeVisible();
    toContainElement(element: HTMLElement | SVGElement | null)
    <span data-testid="ancestor"><span data-testid="descendant"></span></span>
    const ancestor = getByTestId("ancestor");
    const descendant = getByTestId("descendant");
    const nonExistantElement = getByTestId("does-not-exist");
    
    expect(ancestor).toContainElement(descendant);
    expect(descendant).not.toContainElement(ancestor);
    expect(ancestor).not.toContainElement(nonExistantElement);
    toContainHTML(htmlText: string)
    <span data-testid="parent"><span data-testid="child"></span></span>
    // These are valid uses
    expect(getByTestId("parent")).toContainHTML(
      '<span data-testid="child"></span>'
    );
    expect(getByTestId("parent")).toContainHTML('<span data-testid="child" />');
    expect(getByTestId("parent")).not.toContainHTML("<br />");
    
    // These won't work
    expect(getByTestId("parent")).toContainHTML('data-testid="child"');
    expect(getByTestId("parent")).toContainHTML("data-testid");
    expect(getByTestId("parent")).toContainHTML("</span>");
    toHaveAccessibleDescription(expectedAccessibleDescription?: string | RegExp)
    <a
      data-testid="link"
      href="/"
      aria-label="Home page"
      title="A link to start over"
      >Start</a
    >
    <a data-testid="extra-link" href="/about" aria-label="About page">About</a>
    <img src="avatar.jpg" data-testid="avatar" alt="User profile pic" />
    <img
      src="logo.jpg"
      data-testid="logo"
      alt="Company logo"
      aria-describedby="t1"
    />
    <span id="t1" role="presentation">The logo of Our Company</span>
    expect(getByTestId("link")).toHaveAccessibleDescription();
    expect(getByTestId("link")).toHaveAccessibleDescription("A link to start over");
    expect(getByTestId("link")).not.toHaveAccessibleDescription("Home page");
    expect(getByTestId("extra-link")).not.toHaveAccessibleDescription();
    expect(getByTestId("avatar")).not.toHaveAccessibleDescription();
    expect(getByTestId("logo")).not.toHaveAccessibleDescription("Company logo");
    expect(getByTestId("logo")).toHaveAccessibleDescription(
      "The logo of Our Company"
    );
    toHaveAccessibleName(expectedAccessibleName?: string | RegExp)
    <img data-testid="img-alt" src="" alt="Test alt" />
    <img data-testid="img-empty-alt" src="" alt="" />
    <svg data-testid="svg-title"><title>Test title</title></svg>
    <button data-testid="button-img-alt"><img src="" alt="Test" /></button>
    <p><img data-testid="img-paragraph" src="" alt="" /> Test content</p>
    <button data-testid="svg-button"><svg><title>Test</title></svg></p>
    <div><svg data-testid="svg-without-title"></svg></div>
    <input data-testid="input-title" title="test" />
    expect(getByTestId("img-alt")).toHaveAccessibleName("Test alt");
    expect(getByTestId("img-empty-alt")).not.toHaveAccessibleName();
    expect(getByTestId("svg-title")).toHaveAccessibleName("Test title");
    expect(getByTestId("button-img-alt")).toHaveAccessibleName();
    expect(getByTestId("img-paragraph")).not.toHaveAccessibleName();
    expect(getByTestId("svg-button")).toHaveAccessibleName();
    expect(getByTestId("svg-without-title")).not.toHaveAccessibleName();
    expect(getByTestId("input-title")).toHaveAccessibleName();
    toHaveAttribute(attr: string, value?: any)
    <button data-testid="ok-button" type="submit" disabled>ok</button>
    const button = getByTestId("ok-button");
    
    expect(button).toHaveAttribute("disabled");
    expect(button).toHaveAttribute("type", "submit");
    expect(button).not.toHaveAttribute("type", "button");
    
    expect(button).toHaveAttribute("type", expect.stringContaining("sub"));
    expect(button).toHaveAttribute("type", expect.not.stringContaining("but"));
    toHaveClass(...classNames: string[], options?: {exact: boolean})
    <button data-testid="delete-button" class="btn extra btn-danger">
      Delete item
    </button>
    <button data-testid="no-classes">No Classes</button>
    const deleteButton = getByTestId("delete-button");
    const noClasses = getByTestId("no-classes");
    
    expect(deleteButton).toHaveClass("extra");
    expect(deleteButton).toHaveClass("btn-danger btn");
    expect(deleteButton).toHaveClass("btn-danger", "btn");
    expect(deleteButton).not.toHaveClass("btn-link");
    
    expect(deleteButton).toHaveClass("btn-danger extra btn", { exact: true }); // to check if the element has EXACTLY a set of classes
    expect(deleteButton).not.toHaveClass("btn-danger extra", { exact: true }); // if it has more than expected it is going to fail
    
    expect(noClasses).not.toHaveClass();
    toHaveFocus();
    <div><input type="text" data-testid="element-to-focus" /></div>
    const input = getByTestId("element-to-focus");
    
    input.focus();
    expect(input).toHaveFocus();
    
    input.blur();
    expect(input).not.toHaveFocus();
    toHaveFormValues(expectedValues: {
      [name: string]: any
    })
    <form data-testid="login-form">
      <input type="text" name="username" value="jane.doe" />
      <input type="password" name="password" value="12345678" />
      <input type="checkbox" name="rememberMe" checked />
      <button type="submit">Sign in</button>
    </form>
    expect(getByTestId("login-form")).toHaveFormValues({
      username: "jane.doe",
      rememberMe: true,
    });
    toHaveStyle(css: string | object)
    <button
      data-testid="delete-button"
      style="display: none; background-color: red"
    >
      Delete item
    </button>
    const button = getByTestId("delete-button");
    
    expect(button).toHaveStyle("display: none");
    expect(button).toHaveStyle({ display: "none" });
    expect(button).toHaveStyle(`
      background-color: red;
      display: none;
    `);
    expect(button).toHaveStyle({
      backgroundColor: "red",
      display: "none",
    });
    expect(button).not.toHaveStyle(`
      background-color: blue;
      display: none;
    `);
    expect(button).not.toHaveStyle({
      backgroundColor: "blue",
      display: "none",
    });
    toHaveTextContent(text: string | RegExp, options?: {normalizeWhitespace: boolean})
    <span data-testid="text-content">Text Content</span>
    const element = getByTestId("text-content");
    
    expect(element).toHaveTextContent("Content");
    expect(element).toHaveTextContent(/^Text Content$/); // to match the whole content
    expect(element).toHaveTextContent(/content$/i); // to use case-insensitive match
    expect(element).not.toHaveTextContent("content");
    toHaveValue(value: string | string[] | number)
    <input type="text" value="text" data-testid="input-text" />
    <input type="number" value="5" data-testid="input-number" />
    <input type="text" data-testid="input-empty" />
    <select multiple data-testid="select-number">
      <option value="first">First Value</option>
      <option value="second" selected>Second Value</option>
      <option value="third" selected>Third Value</option>
    </select>
    const textInput = getByTestId("input-text");
    const numberInput = getByTestId("input-number");
    const emptyInput = getByTestId("input-empty");
    const selectInput = getByTestId("select-number");
    
    expect(textInput).toHaveValue("text");
    expect(numberInput).toHaveValue(5);
    expect(emptyInput).not.toHaveValue();
    expect(selectInput).toHaveValue(["second", "third"]);
    toHaveDisplayValue(value: string | RegExp | (string|RegExp)[])
    <label for="input-example">First name</label>
    <input type="text" id="input-example" value="Luca" />
    
    <label for="textarea-example">Description</label>
    <textarea id="textarea-example">An example description here.</textarea>
    
    <label for="single-select-example">Fruit</label>
    <select id="single-select-example">
      <option value="">Select a fruit...</option>
      <option value="banana">Banana</option>
      <option value="ananas">Ananas</option>
      <option value="avocado">Avocado</option>
    </select>
    
    <label for="multiple-select-example">Fruits</label>
    <select id="multiple-select-example" multiple>
      <option value="">Select a fruit...</option>
      <option value="banana" selected>Banana</option>
      <option value="ananas">Ananas</option>
      <option value="avocado" selected>Avocado</option>
    </select>
    const input = screen.getByLabelText("First name");
    const textarea = screen.getByLabelText("Description");
    const selectSingle = screen.getByLabelText("Fruit");
    const selectMultiple = screen.getByLabelText("Fruits");
    
    expect(input).toHaveDisplayValue("Luca");
    expect(input).toHaveDisplayValue(/Luc/);
    expect(textarea).toHaveDisplayValue("An example description here.");
    expect(textarea).toHaveDisplayValue(/example/);
    expect(selectSingle).toHaveDisplayValue("Select a fruit...");
    expect(selectSingle).toHaveDisplayValue(/Select/);
    expect(selectMultiple).toHaveDisplayValue([/Avocado/, "Banana"]);
    toBeChecked();
    <input type="checkbox" checked data-testid="input-checkbox-checked" />
    <input type="checkbox" data-testid="input-checkbox-unchecked" />
    <div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
    <div
      role="checkbox"
      aria-checked="false"
      data-testid="aria-checkbox-unchecked"
    />
    
    <input type="radio" checked value="foo" data-testid="input-radio-checked" />
    <input type="radio" value="foo" data-testid="input-radio-unchecked" />
    <div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
    <div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
    <div role="switch" aria-checked="true" data-testid="aria-switch-checked" />
    <div role="switch" aria-checked="false" data-testid="aria-switch-unchecked" />
    const inputCheckboxChecked = getByTestId("input-checkbox-checked");
    const inputCheckboxUnchecked = getByTestId("input-checkbox-unchecked");
    const ariaCheckboxChecked = getByTestId("aria-checkbox-checked");
    const ariaCheckboxUnchecked = getByTestId("aria-checkbox-unchecked");
    expect(inputCheckboxChecked).toBeChecked();
    expect(inputCheckboxUnchecked).not.toBeChecked();
    expect(ariaCheckboxChecked).toBeChecked();
    expect(ariaCheckboxUnchecked).not.toBeChecked();
    
    const inputRadioChecked = getByTestId("input-radio-checked");
    const inputRadioUnchecked = getByTestId("input-radio-unchecked");
    const ariaRadioChecked = getByTestId("aria-radio-checked");
    const ariaRadioUnchecked = getByTestId("aria-radio-unchecked");
    expect(inputRadioChecked).toBeChecked();
    expect(inputRadioUnchecked).not.toBeChecked();
    expect(ariaRadioChecked).toBeChecked();
    expect(ariaRadioUnchecked).not.toBeChecked();
    
    const ariaSwitchChecked = getByTestId("aria-switch-checked");
    const ariaSwitchUnchecked = getByTestId("aria-switch-unchecked");
    expect(ariaSwitchChecked).toBeChecked();
    expect(ariaSwitchUnchecked).not.toBeChecked();
    toBePartiallyChecked();
    <input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
    <input type="checkbox" checked data-testid="input-checkbox-checked" />
    <input type="checkbox" data-testid="input-checkbox-unchecked" />
    <div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
    <div
      role="checkbox"
      aria-checked="false"
      data-testid="aria-checkbox-unchecked"
    />
    <input type="checkbox" data-testid="input-checkbox-indeterminate" />
    const ariaCheckboxMixed = getByTestId("aria-checkbox-mixed");
    const inputCheckboxChecked = getByTestId("input-checkbox-checked");
    const inputCheckboxUnchecked = getByTestId("input-checkbox-unchecked");
    const ariaCheckboxChecked = getByTestId("aria-checkbox-checked");
    const ariaCheckboxUnchecked = getByTestId("aria-checkbox-unchecked");
    const inputCheckboxIndeterminate = getByTestId("input-checkbox-indeterminate");
    
    expect(ariaCheckboxMixed).toBePartiallyChecked();
    expect(inputCheckboxChecked).not.toBePartiallyChecked();
    expect(inputCheckboxUnchecked).not.toBePartiallyChecked();
    expect(ariaCheckboxChecked).not.toBePartiallyChecked();
    expect(ariaCheckboxUnchecked).not.toBePartiallyChecked();
    
    inputCheckboxIndeterminate.indeterminate = true;
    expect(inputCheckboxIndeterminate).toBePartiallyChecked();
    toHaveErrorMessage(text: string | RegExp)
    <label for="startTime"> Please enter a start time for the meeting: </label>
    <input
      id="startTime"
      type="text"
      aria-errormessage="msgID"
      aria-invalid="true"
      value="11:30 PM"
    />
    <span id="msgID" aria-live="assertive" style="visibility:visible">
      Invalid time: the time must be between 9:00 AM and 5:00 PM
    </span>
    const timeInput = getByLabel("startTime");
    
    expect(timeInput).toHaveErrorMessage(
      "Invalid time: the time must be between 9:00 AM and 5:00 PM"
    );
    expect(timeInput).toHaveErrorMessage(/invalid time/i); // to partially match
    expect(timeInput).toHaveErrorMessage(expect.stringContaining("Invalid time")); // to partially match
    expect(timeInput).not.toHaveErrorMessage("Pikachu!");
    toBeEmpty();
    <span data-testid="not-empty"><span data-testid="empty"></span></span>
    expect(getByTestId("empty")).toBeEmpty();
    expect(getByTestId("not-empty")).not.toBeEmpty();
    toBeInTheDOM();
    expect(document.querySelector(".ok-button")).toBeInstanceOf(HTMLElement);
    expect(document.querySelector(".cancel-button")).toBeTruthy();
    toHaveDescription(text: string | RegExp)
    <button aria-label="Close" aria-describedby="description-close">X</button>
    <div id="description-close">Closing will discard any changes</div>
    
    <button>Delete</button>
    const closeButton = getByRole("button", { name: "Close" });
    
    expect(closeButton).toHaveDescription("Closing will discard any changes");
    expect(closeButton).toHaveDescription(/will discard/); // to partially match
    expect(closeButton).toHaveDescription(expect.stringContaining("will discard")); // to partially match
    expect(closeButton).toHaveDescription(/^closing/i); // to use case-insensitive match
    expect(closeButton).not.toHaveDescription("Other description");
    
    const deleteButton = getByRole("button", { name: "Delete" });
    expect(deleteButton).not.toHaveDescription();
    expect(deleteButton).toHaveDescription(""); // Missing or empty description always becomes a blank string
    import { render, screen } from "@testing-library/react";
    
    render(<MyComponent />);
    const deleteElement = screen.getByTitle("Delete");
    const closeElement = screen.getByTitle("Close");
    cy.findByTitle("Delete").should("exist");
    cy.findByTitle("Close").should("exist");
    npm install --save-dev @testing-library/react @testing-library/jest-dom
    // import React so you can use JSX (React.createElement) in your test
    import React from "react";
    
    /**
     * render: lets us render the component as React would
     * screen: a utility for finding elements the same way the user does
     */
    import { render, screen } from "@testing-library/react";
    test("test title", () => {
      // Your tests come here...
    });
    const Welcome = (props) => {
      const [values, setValues] = useState({
        firstName: props.firstName,
        lastName: props.lastName,
      });
    
      const handleChange = (event) => {
        setValues({ ...values, [event.target.name]: event.target.value });
      };
    
      return (
        <div>
          <h1>
            Welcome, {values.firstName} {values.lastName}
          </h1>
    
          <form name="userName">
            <label>
              First Name
              <input
                value={values.firstName}
                name="firstName"
                onChange={handleChange}
              />
            </label>
    
            <label>
              Last Name
              <input
                value={values.lastName}
                name="lastName"
                onChange={handleChange}
              />
            </label>
          </form>
        </div>
      );
    };
    
    export default Welcome;
    test("has correct welcome text", () => {
      const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />);
      expect(wrapper.find("h1").text()).toEqual("Welcome, John Doe");
    });
    test("has correct welcome text", () => {
      render(<Welcome firstName="John" lastName="Doe" />);
      expect(screen.getByRole("heading")).toHaveTextContent("Welcome, John Doe");
    });
    test("has correct input value", () => {
      const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />);
      expect(wrapper.find('input[name="firstName"]').value).toEqual("John");
      expect(wrapper.find('input[name="lastName"]').value).toEqual("Doe");
    });
    test("has correct input value", () => {
      render(<Welcome firstName="John" lastName="Doe" />);
      expect(screen.getByRole("form")).toHaveFormValues({
        firstName: "John",
        lastName: "Doe",
      });
    });
    npm install --save-dev @testing-library/user-event @testing-library/dom
    import userEvent from "@testing-library/user-event";
    import React from "react";
    
    const Checkbox = () => {
      return (
        <div>
          <label htmlFor="checkbox">Check</label>
          <input id="checkbox" type="checkbox" />
        </div>
      );
    };
    
    export default Checkbox;
    test("handles click correctly", async () => {
      render(<Checkbox />);
      const user = userEvent.setup();
    
      // You can also call this method directly on userEvent,
      // but using the methods from `.setup()` is recommended.
      await user.click(screen.getByText("Check"));
    
      expect(screen.getByLabelText("Check")).toBeChecked();
    });
    enum Direction {  Up = 1,  Down,  Left,  Right,}Try
    enum Direction {  Up,  Down,  Left,  Right,}Try
    enum UserResponse {  No = 0,  Yes = 1,} function respond(recipient: string, message: UserResponse): void {
    // ...} respond("Princess Caroline", UserResponse.Yes);Try
    enum E {  A = getSomeValue(),  B,Enum member must have initializer.Enum member must have initializer.}Try
    enum Direction {  Up = "UP",  Down = "DOWN",  Left = "LEFT",  Right = "RIGHT",}Try
    enum BooleanLikeHeterogeneousEnum {  No = 0,  Yes = "YES",}Try
    // E.X is
    constant:enum E {  X,}Try
        ```
    
    *   It does not have an initializer and the preceding enum member was a _numeric_
    constant. In this case the value of the current enum member will be the value of the preceding enum member plus one.
    // All enum members in 'E1' and 'E2' are
    constant. enum E1 {  X,  Y,  Z,} enum E2 {  A = 1,  B,  C,}Try
    ```
    enum FileAccess {
    //
    constant members  None,  Read = 1 << 1,  Write = 1 << 2,  ReadWrite = Read | Write,
    // computed member  G = "123".length,}Try
    enum ShapeKind {  Circle,  Square,} interface Circle {  kind: ShapeKind.Circle;  radius: number;} interface Square {  kind: ShapeKind.Square;  sideLength: number;} let c: Circle = {  kind: ShapeKind.Square,Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.  radius: 100,};Try
    enum E {  Foo,  Bar,} function f(x: E) {  if (x !== E.Foo || x !== E.Bar) {This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
    //  }}Try
    enum E {
      X,
      Y,
      Z,
    }
    Try;
    enum E {
      X,
      Y,
      Z,
    }
    function f(obj: { X: number }) {
      return obj.X;
    }
    // Works, since 'E' has a property named 'X' which is a number.f(E);Try
    enum LogLevel {  ERROR,  WARN,  INFO,  DEBUG,} /** * This is equivalent to: * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; */type LogLevelStrings = keyof typeof LogLevel; function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key];  if (num <= LogLevel.WARN) {    console.log("Log level key is:", key);    console.log("Log level value is:", num);    console.log("Log level message is:", message);  }}printImportant("ERROR", "This is a message");Try
    enum Enum {
      A,
    }
    let a = Enum.A;
    let nameOfA = Enum[a];
    // "A"Try
    "use strict";
    var Enum;
    (function (Enum) {
      Enum[(Enum["A"] = 0)] = "A";
    })(Enum || (Enum = {}));
    let a = Enum.A;
    let nameOfA = Enum[a];
    // "A" Try
    const enum Enum {
      A = 1,
      B = A * 2,
    }
    Try;
    const enum Direction {
      Up,
      Down,
      Left,
      Right,
    }
    let directions = [
      Direction.Up,
      Direction.Down,
      Direction.Left,
      Direction.Right,
    ];
    Try;
    "use strict";
    let directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
    Try;
    declare enum Enum {  A = 1,  B,  C = 2,}Try
    const enum EDirection {  Up,  Down,  Left,  Right,}
    const ODirection = {  Up: 0,  Down: 1,  Left: 2,  Right: 3,} as
    const; EDirection.Up;           (enum member) EDirection.Up = 0 ODirection.Up;           (property) Up: 0
    // Using the enum as a parameterfunction walk(dir: EDirection) {}
    // It requires an extra line to pull out the valuestype Direction = typeof ODirection[keyof typeof ODirection];function run(dir: Direction) {} walk(EDirection.Left);run(ODirection.Right);Try
    <div
      id="percent-loaded"
      role="progressbar"
      aria-valuenow="75"
      aria-valuemin="0"
      aria-valuemax="100"
    ></div>
    // Find the progress bar <div> in the DOM.
    var progressBar = document.getElementById("percent-loaded");
    
    // Set its ARIA roles and states,
    // so that assistive technologies know what kind of widget it is.
    progressBar.setAttribute("role", "progressbar");
    progressBar.setAttribute("aria-valuemin", 0);
    progressBar.setAttribute("aria-valuemax", 100);
    
    // Create a function that can be called at any time to update
    // the value of the progress bar.
    function updateProgress(percentComplete) {
      progressBar.setAttribute("aria-valuenow", percentComplete);
    }
    <progress id="percent-loaded" value="75" max="100">75 %</progress>
    getByLabelText(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      text: TextMatch,
      options?: {
        selector?: string = '*',
        exact?: boolean = true,
        normalizer?: NormalizerFn,
      }): HTMLElement
    // for/htmlFor relationship between label and form element id
    <label for="username-input">Username</label>
    <input id="username-input" />
    
    // The aria-labelledby attribute with form elements
    <label id="username-label">Username</label>
    <input aria-labelledby="username-label" />
    
    // Wrapper labels
    <label>Username <input /></label>
    
    // Wrapper labels where the label text is in another child element
    <label>
      <span>Username</span>
      <input />
    </label>
    
    // aria-label attributes
    // Take care because this is not a label that users can see on the page,
    // so the purpose of your input must be obvious to visual users.
    <input aria-label="Username" />
    import { screen } from "@testing-library/dom";
    
    const inputNode = screen.getByLabelText("Username");
    import { render, screen } from "@testing-library/react";
    
    render(<Login />);
    
    const inputNode = screen.getByLabelText("Username");
    cy.findByLabelText("Username").should("exist");
    // Multiple elements labelled via aria-labelledby
    <label id="username">Username</label>
    <input aria-labelledby="username" />
    <span aria-labelledby="username">Please enter your username</span>
    
    // Multiple labels with the same text
    <label>
      Username
      <input />
    </label>
    <label>
      Username
      <textarea></textarea>
    </label>
    const inputNode = screen.getByLabelText("Username", { selector: "input" });
    // This case is not valid
    // for/htmlFor between label and an element that is not a form element
    <section id="photos-section">
      <label for="photos-section">Photos</label>
    </section>
    fireEvent(node: HTMLElement, event: Event)
    // <button>Submit</button>fireEvent(  getByText(container, 'Submit'),  new MouseEvent('click', {    bubbles: true,    cancelable: true,  }),)
    fireEvent[eventName](node: HTMLElement, eventProperties: Object)
    fireEvent.change(getByLabelText(/username/i), { target: { value: "a" } });
    // note: attempting to manually set the files property of an HTMLInputElement
    // results in an error as the files property is read-only.
    // this feature works around that by using Object.defineProperty.fireEvent.change(getByLabelText(/picture/i), {  target: {    files: [new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'})],  },})
    // Note: The 'value' attribute must use ISO 8601 format when firing a
    // change event on an input of type "date". Otherwise the element will not
    // reflect the changed value.
    // Invalid:fireEvent.change(input, {target: {value: '24/05/2020'}})
    // Valid:fireEvent.change(input, {target: {value: '2020-05-24'}})
    fireEvent.drop(getByLabelText(/drop files here/i), {
      dataTransfer: {
        files: [new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" })],
      },
    });
    fireEvent.keyDown(domNode, {key: 'Enter', code: 'Enter', charCode: 13})fireEvent.keyDown(domNode, {key: 'A', code: 'KeyA'})
    createEvent[eventName](node: HTMLElement, eventProperties: Object)
    const myEvent = createEvent.click(node, {button: 2})fireEvent(node, myEvent)
    // myEvent.timeStamp can be accessed just like any other properties from myEvent
    // note: The access to the events created by `createEvent` is based on the native event API,
    // Therefore, native properties of HTMLEvent object (e.g. `timeStamp`, `cancelable`, `type`) should be set using Object.defineProperty
    // For more info see: https:
    //developer.mozilla.org/en-US/docs/Web/API/Event
    // simulate the 'input' event on a file inputfireEvent(  input,  createEvent('input', input, {    target: {files: inputFiles},    ...init,  }),)
    import {render, screen, fireEvent} from '@testing-library/react'
    const Button = ({onClick, children}) => (
    <button onClick={onClick}>{children}</button>)test('calls onClick prop when clicked', () => {
    const handleClick = jest.fn()  render(<Button onClick={handleClick}>Click Me</Button>)  fireEvent.click(screen.getByText(/click me/i))  expect(handleClick).toHaveBeenCalledTimes(1)})

    [Component Story Format](https: //storybook.js.org/blog/component-story-format/) - read more about the Component Story Format (CSF), a new way to author stories based on ES6 modules

    arrow-up-right
    https: //duke-2.gitbook.io/duke
    https://duke-3.gitbook.io/duke/arrow-up-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2658%20(1).mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2659.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2658-1.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/dnt-2724-accordion-testing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/2654-a11y-audit-form-errors.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/analytics.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/definitionofdone.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/technicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/practicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/sitecore.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/intro.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/setup.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/forms.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/tailwind-css.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/storybook.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/ideas.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/broken-reference/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/aria.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/ke-energy.mdchevron-right
    ⚙️Testingchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2658.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2659.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/dnt-2724-accordion-testing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/2654-a11y-audit-form-errors.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/analytics.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/definitionofdone.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/technicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/practicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/sitecore.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/intro.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/setup.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/forms.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/tailwind-css.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/storybook.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/ideas.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/broken-reference/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/aria.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript-types.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/types-vs-interfaces.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript-interfaces.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/enums.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/types-vs-interfaces-1.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript-rules/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessability/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessability/focus-order.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessability/aria-accessibility.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/testing/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/testing/testing-input.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/react-testing-library/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/most-useful/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/most-useful/tailwind-classes/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/most-useful/bookmarks.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/website/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/website/overview/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/website/overview/duke-energy-manual-audit_report.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/live-deploy.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/intro.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/technicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/practicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/analytics.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/creating-components/sitecore/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/creating-components/sitecore/sitecore-docs.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/definitionofdone.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/setup.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/testing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/forms.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/storybook/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/storybook/official-tutorial/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/storybook/official-tutorial/component-driven-design.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/code-splitting.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/svgloader.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/tailwind-css.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/unit-tests.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/hooks-api-reference-react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/using-the-effect-hook-react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/getting-started-with-react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/react-todolist.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/componentizing-our-react-app.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/react-interactivity-events-and-state.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/react-interactivity-editing-filtering-conditional-rendering.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/create-react-app.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/react-components.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/handling-events.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/testing-assignment/react-testing-recipes.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/webpack.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/curry-vs-functional-composition/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/curry-vs-functional-composition/argv.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/bubbling-and-capturing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/modules.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/private-npm-packages.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/aria-labelledby-accessibility.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/focus.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/optional-chaning.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/promises.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/enums-in-javascript.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/jira.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/airbnb-javascript-style-guide.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/performance.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/sitecore/sitecore/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/sitecore/sitecore/dxt-solution.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/index.tsx.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/composition.tsx.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/data.js.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/types.ts.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/stories.js.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/test.tsx.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/testing-assignment.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/day-4.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/day-2.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/day-3.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-3.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-4.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-1.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-5.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-4.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-2.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-3.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-5.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-6.mdchevron-right
    hashtag
    Overview

    Queries are the methods that Testing Library gives you to find elements on the page. There are several types of queriesarrow-up-right ("get", "find", "query"); the difference between them is whether the query will throw an error if no element is found or if it will return a Promise and retry. Depending on what page content you are selecting, different queries may be more or less appropriate. See the priority guidearrow-up-right for recommendations on how to make use of semantic queries to test your page in the most accessible way.

    After selecting an element, you can use the Events APIarrow-up-right or user-eventarrow-up-right to fire events and simulate user interactions with the page, or use Jest and jest-domarrow-up-right to make assertions about the element.

    There are Testing Library helper methods that work with queries. As elements appear and disappear in response to actions, Async APIsarrow-up-right like waitForarrow-up-right or findBy queriesarrow-up-right can be used to await the changes in the DOM. To find only elements that are children of a specific element, you can use withinarrow-up-right. If necessary, there are also a few options you can configurearrow-up-right, like the timeout for retries and the default testID attribute.

    hashtag
    Example

    hashtag
    Types of Queries

    • Single Elements

      • getBy...: Returns the matching node for a query, and throw a descriptive error if no elements match or if more than one match is found (use getAllBy instead if more than one element is expected).

      • queryBy...: Returns the matching node for a query, and return null if no elements match. This is useful for asserting an element that is not present. Throws an error if more than one match is found (use queryAllBy instead if this is OK).

      • findBy...: Returns a Promise which resolves when an element is found which matches the given query. The promise is rejected if no element is found or if more than one element is found after a default timeout of 1000ms. If you need to find more than one element, use findAllBy.

    • Multiple Elements

      • getAllBy...: Returns an array of all matching nodes for a query, and throws an error if no elements match.

      • queryAllBy...: Returns an array of all matching nodes for a query, and return an empty array (

    Summary Table

    Type of Query
    0 Matches
    1 Match
    >1 Matches
    Retry (Async/Await)

    Single Element

    getBy...

    Throw error

    Return element

    hashtag
    Priority

    Based on the Guiding Principlesarrow-up-right, your test should resemble how users interact with your code (component, page, etc.) as much as possible. With this in mind, we recommend this order of priority:

    1. Queries Accessible to Everyone Queries that reflect the experience of visual/mouse users as well as those that use assistive technology.

      1. getByRole: This can be used to query every element that is exposed in the accessibility treearrow-up-right. With the name option you can filter the returned elements by their accessible namearrow-up-right. This should be your top preference for just about everything. There's not much you can't get with this (if you can't, it's possible your UI is inaccessible). Most often, this will be used with the name option like so: getByRole('button', {name: /submit/i}). Check the .

      2. getByLabelText: This method is really good for form fields. When navigating through a website form, users find elements using label text. This method emulates that behavior, so it should be your top preference.

      3. getByPlaceholderText: . But if that's all you have, then it's better than alternatives.

      4. getByText: Outside of forms, text content is the main way users find elements. This method can be used to find non-interactive elements (like divs, spans, and paragraphs).

      5. getByDisplayValue: The current value of a form element can be useful when navigating a page with filled-in values.

    2. Semantic Queries HTML5 and ARIA compliant selectors. Note that the user experience of interacting with these attributes varies greatly across browsers and assistive technology.

      1. getByAltText: If your element is one which supports alt text (img, area, input, and any custom element), then you can use this to find that element.

    3. Test IDs

      1. getByTestId: The user cannot see (or hear) these, so this is only recommended for cases where you can't match by role or text or it doesn't make sense (e.g. the text is dynamic).

    hashtag
    Using Queries

    The base queries from DOM Testing Library require you to pass a container as the first argument. Most framework-implementations of Testing Library provide a pre-bound version of these queries when you render your components with them which means you do not have to provide a container. In addition, if you just want to query document.body then you can use the screenarrow-up-right export as demonstrated below (using screen is recommended).

    The primary argument to a query can be a string, regular expression, or function. There are also options to adjust how node text is parsed. See TextMatcharrow-up-right for documentation on what can be passed to a query.

    Given the following DOM elements (which can be rendered by React, Vue, Angular, or plain HTML code):

    You can use a query to find an element (byLabelText, in this case):

    hashtag
    screen

    All of the queries exported by DOM Testing Library accept a container as the first argument. Because querying the entire document.body is very common, DOM Testing Library also exports a screen object which has every query that is pre-bound to document.body (using the withinarrow-up-right functionality). Wrappers such as React Testing Library re-export screen so you can use it the same way.

    Here's how you use it:

    • Native

    • React

    • Cypress

    Note

    You need a global DOM environment to use screen. If you're using jest, with the testEnvironmentarrow-up-right set to jsdom, a global DOM environment will be available for you.

    If you're loading your test with a script tag, make sure it comes after the body. An example can be seen herearrow-up-right.

    hashtag
    TextMatch

    Most of the query APIs take a TextMatch as an argument, which means the argument can be either a string, regex, or a function which returns true for a match and false for a mismatch.

    hashtag
    TextMatch Examples

    Given the following HTML:

    Will** find the div:**

    Will not** find the div:**

    hashtag
    Precision

    Queries that take a TextMatch also accept an object as the final argument that can contain options that affect the precision of string matching:

    • exact: Defaults to true; matches full strings, case-sensitive. When false, matches substrings and is not case-sensitive.

      • exact has no effect on regex or function arguments.

      • In most cases using a regex instead of a string gives you more control over fuzzy matching and should be preferred over { exact: false }.

    • normalizer: An optional function which overrides normalization behavior. See .

    hashtag
    Normalization

    Before running any matching logic against text in the DOM, DOM Testing Library automatically normalizes that text. By default, normalization consists of trimming whitespace from the start and end of text, and collapsing multiple adjacent whitespace characters into a single space.

    If you want to prevent that normalization, or provide alternative normalization (e.g. to remove Unicode control characters), you can provide a normalizer function in the options object. This function will be given a string and is expected to return a normalized version of that string.

    Note

    Specifying a value for normalizer replaces the built-in normalization, but you can call getDefaultNormalizer to obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer.

    getDefaultNormalizer takes an options object which allows the selection of behaviour:

    • trim: Defaults to true. Trims leading and trailing whitespace

    • collapseWhitespace: Defaults to true. Collapses inner whitespace (newlines, tabs, repeated spaces) into a single space.

    Normalization Examples

    To perform a match against text without trimming:

    To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:

    hashtag
    Debugging

    hashtag
    screen.debug()

    For convenience screen also exposes a debug method in addition to the queries. This method is essentially a shortcut for console.log(prettyDOM()). It supports debugging the document, a single element, or an array of elements.

    hashtag
    screen.logTestingPlaygroundURL()

    For debugging using testing-playgroundarrow-up-right, screen exposes this convenient method which logs a URL that can be opened in a browser.

    hashtag
    Manual Queries

    On top of the queries provided by the testing library, you can use the regular querySelector DOM APIarrow-up-right to query elements. Note that using this as an escape hatch to query by class or id is not recommended because they are invisible to the user. Use a testid if you have to, to make your intention to fall back to non-semantic queries clear and establish a stable API contract in the HTML.

    hashtag
    Browser extension

    Do you still have problems knowing how to use Testing Library queries?

    There is a very cool Browser extension for Chromearrow-up-right and Firefoxarrow-up-right named Testing Playground, and it helps you find the best queries to select elements. It allows you to inspect the element hierarchies in the Browser's Developer Tools, and provides you with suggestions on how to select them, while encouraging good testing practices.

    hashtag
    Playground

    If you want to get more familiar with these queries, you can try them out on testing-playground.comarrow-up-right. Testing Playground is an interactive sandbox where you can run different queries against your own html, and get visual feedback matching the rules mentioned above.

    Jump to Table of Contentsarrow-up-right Collapse Sidebararrow-up-right

    hashtag
    Abstract

    This document provides readers with an understanding of how to use WAI-ARIA 1.1arrow-up-right [WAI-ARIAarrow-up-right] to create accessible rich internet applications. It describes considerations that might not be evident to most authors from the WAI-ARIA specification alone and recommends approaches to make widgets, navigation, and behaviors accessible using WAI-ARIA roles, states, and properties. This document is directed primarily to Web application developers, but the guidance is also useful for user agent and assistive technology developers.

    This document is part of the WAI-ARIA suite described in the WAI-ARIA Overviewarrow-up-right.

    hashtag
    Status of This Document

    This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports indexarrow-up-right at https://www.w3.org/TR/.

    This is the WAI-ARIA Authoring Practices 1.1 Working Group Notearrow-up-right by the Accessible Rich Internet Applications Working Grouparrow-up-right. It supports the Accessible Rich Internet Applications 1.1 W3C Recommendationarrow-up-right [wai-aria-1.1arrow-up-right], providing detailed advice and examples beyond what would be appropriate to a technical specification but which are important to understand the specification.

    WAI-ARIA Authoring Practices 1.1 was previously published as a Working Group Note in December 2017, to accompany the WAI-ARIA 1.1 Recommendation, and was republished in July 2018 and February 2019 with additional design pattern and examples, quality improvements, and improveed support for WAI-ARIA 1.1. Details of changes are described in the change logarrow-up-right. Separately, WAI-ARIA Authoring Practices 1.2arrow-up-right includes the improvements in this document plus additional features specific to WAI-ARIA 1.2arrow-up-right.

    To comment, file an issue in the W3C ARIA Practices GitHub repositoryarrow-up-right, or if that is not possible, send email to public-aria@w3.orgenvelope (comment archivearrow-up-right).

    This document was published by the Accessible Rich Internet Applications Working Grouparrow-up-right as a Working Group Note.

    Comments regarding this document are welcome. Please send them to public-aria@w3.orgenvelope (archivesarrow-up-right).

    Publication as a Working Group Note does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

    This document was produced by a group operating under the W3C Patent Policyarrow-up-right.

    This document is governed by the 1 March 2019 W3C Process Documentarrow-up-right.

    hashtag
    Table of Contents

    1. 1. Introductionarrow-up-right

    2. 2. Read Me Firstarrow-up-right

      1. 2.1 No ARIA is better than Bad ARIAarrow-up-right

    3. 1. 2. 3. 4. 5.

    4. 1. 2. 3. 4.

    5. 1.

    hashtag
    1. Introduction

    This section is informative.

    WAI-ARIA Authoring Practices is a guide for understanding how to use WAI-ARIA 1.1arrow-up-right to create an accessible Rich Internet Application. It provides guidance on the appropriate application of WAI-ARIA, describes recommended WAI-ARIA usage patterns, and explains concepts behind them.

    Languages used to create rich and dynamic web sites, e.g., HTML, JavaScript, CSS, and SVG, do not natively include all the features required to make sites usable by people who use assistive technologies (AT) or who rely on keyboard navigation. The W3C Web Accessibility Initiative's (WAI) Accessible Rich Internet Applications working group (ARIA WG) is addressing these deficiencies through several W3C standards efforts. The WAI-ARIA Overviewarrow-up-right provides additional background on WAI-ARIA, summarizes those efforts, and lists the other documents included in the WAI-ARIA suite.

    After a brief Read Me First section, the guide begins with ARIA implementation patterns for common widgets that both enumerate expected behaviors and demonstrate those behaviors with working code. The implementation patterns and examples refer to detailed explanations of supporting concepts in subsequent guidance sections. The guidance sections cover more general topics such as use of ARIA landmarks, practices for keyboard interfaces, grid and table properties, and the effects of role presentation.

    hashtag
    2. Read Me First

    hashtag
    2.1 No ARIA is better than Bad ARIA

    Functionally, ARIA roles, states, and properties are analogous to a CSS for assistive technologies. For screen reader users, ARIA controls the rendering of their non-visual experience. Incorrect ARIA misrepresents visual experiences, with potentially devastating effects on their corresponding non-visual experiences.

    Before using ARIA or any of the guidance in this document, please take time to understand the following two essential principles.

    Principle 1: A role is a promise

    This code:

    Is a promise that the author of that <div> has also incorporated JavaScript that provides the keyboard interactions expected for a button. Unlike HTML input elements, ARIA roles do not cause browsers to provide keyboard behaviors or styling.

    Using a role without fulfilling the promise of that role is similar to making a "Place Order" button that abandons an order and empties the shopping cart.

    One of the objectives of this guide is to define expected behaviors for each ARIA role.

    Principle 2: ARIA Can Both Cloak and Enhance, Creating Both Power and Danger

    The information assistive technologies need about the meaning and purpose of user interface elements is called accessibility semantics. From the perspective of assistive technologies, ARIA gives authors the ability to dress up HTML and SVG elements with critical accessibility semantics that the assistive technologies would not otherwise be able to reliably derive.

    Some of ARIA is like a cloak; it covers up, or overrides, the original semantics or content.

    On the other hand, some uses of ARIA are more like suspenders or belts; they add meaning that provides essential support to the original content.

    This is the power of ARIA. It enables authors to describe nearly any user interface component in ways that assistive technologies can reliably interpret, thus making components accessible to assistive technology users.

    This is also the danger of ARIA. Authors can inadvertently override accessibility semantics.

    hashtag
    2.2 Browser and Assistive Technology Support

    Testing assistive technology interoperability is essential before using code from this guide in production. Because the purpose of this guide is to illustrate appropriate use of ARIA 1.1 as defined in the ARIA specification, the design patterns, reference examples, and sample code intentionally do not describe and implement coding techniques for working around problems caused by gaps in support for ARIA 1.1 in browsers and assistive technologies. It is thus advisable to test implementations thoroughly with each browser and assistive technology combination that is relevant within a target audience.

    Similarly, JavaScript and CSS in this guide is written to be compatible with the most recent version of Chrome, Firefox, Internet Explorer, and Safari at the time of writing. In particular, some JavaScript and CSS may not function correctly in Internet Explorer version 10 or earlier.

    Except in cases where the ARIA Working Group and other contributors have overlooked an error, examples in this guide that do not function well in a particular browser or with a specific assistive technology are demonstrating browser or assistive technology bugs. Browser and assistive technology developers can thus utilize code in this guide to help assess the quality of their support for ARIA 1.1.

    hashtag
    2.3 Mobile and Touch Support

    Currently, this guide does not indicate which examples are compatible with mobile browsers or touch interfaces. While some of the examples include specific features that enhance mobile and touch support, some ARIA features are not supported in any mobile browser. In addition, there is not yet a standardized approach for providing touch interactions that work across mobile browsers.

    More guidance about touch and mobile support is planned for future releases of the guide.

    This section demonstrates how to make common rich internet application patterns and widgets accessible by applying WAI-ARIA roles, states, and properties and implementing keyboard support.

    hashtag
    3.1 Accordion (Sections With Show/Hide Functionality)

    An accordion is a vertically stacked set of interactive headings that each contain a title, content snippet, or thumbnail representing a section of content. The headings function as controls that enable users to reveal or hide their associated sections of content. Accordions are commonly used to reduce the need to scroll when presenting multiple sections of content on a single page.

    Terms for understanding accordions include:

    Accordion Header:

    Label for or thumbnail representing a section of content that also serves as a control for showing, and in some implementations, hiding the section of content.

    Accordion Panel:

    Section of content associated with an accordion header.

    In some accordions, there are additional elements that are always visible adjacent to the accordion header. For instance, a menubutton may accompany each accordion header to provide access to actions that apply to that section. And, in some cases, a snippet of the hidden content may also be visually persistent.

    Example

    Accordion Examplearrow-up-right: demonstrates a form divided into three sections using an accordion to show one section at a time.

    Keyboard Interaction

    • Enter or Space:

      • When focus is on the accordion header for a collapsed panel, expands the associated panel. If the implementation allows only one panel to be expanded, and if another panel is expanded, collapses that panel.

      • When focus is on the accordion header for an expanded panel, collapses the panel if the implementation supports collapsing. Some implementations require one panel to be expanded at all times and allow only one panel to be expanded; so, they do not support a collapse function.

    • Tab: Moves focus to the next focusable element; all focusable elements in the accordion are included in the page Tab sequence.

    • Shift + Tab: Moves focus to the previous focusable element; all focusable elements in the accordion are included in the page Tab sequence.

    • Down Arrow (Optional): If focus is on an accordion header, moves focus to the next accordion header. If focus is on the last accordion header, either does nothing or moves focus to the first accordion header.

    • Up Arrow (Optional): If focus is on an accordion header, moves focus to the previous accordion header. If focus is on the first accordion header, either does nothing or moves focus to the last accordion header.

    • Home (Optional): When focus is on an accordion header, moves focus to the first accordion header.

    • End (Optional): When focus is on an accordion header, moves focus to the last accordion header.

    WAI-ARIA Roles, States, and Properties:

    • The title of each accordion header is contained in an element with role buttonarrow-up-right.

    • Each accordion header button is wrapped in an element with role headingarrow-up-right that has a value set for aria-levelarrow-up-right that is appropriate for the information architecture of the page.

      • If the native host language has an element with an implicit heading and aria-level, such as an HTML heading tag, a native host language element may be used.

      • The button element is the only element inside the heading element. That is, if there are other visually persistent elements, they are not included inside the heading element.

    • If the accordion panel associated with an accordion header is visible, the header button element has set to true. If the panel is not visible, is set to false.

    • The accordion header button element has set to the ID of the element containing the accordion panel content.

    • If the accordion panel associated with an accordion header is visible, and if the accordion does not permit the panel to be collapsed, the header button element has set to true.

    • Optionally, each element that serves as a container for panel content has role and with a value that refers to the button that controls display of the panel.

      • Avoid using the region role in circumstances that create landmark region proliferation, e.g., in an accordion that contains more than approximately 6 panels that can be expanded at the same time.

    hashtag
    3.2 Alert

    An alertarrow-up-right is an element that displays a brief, important message in a way that attracts the user's attention without interrupting the user's task. Dynamically rendered alerts are automatically announced by most screen readers, and in some operating systems, they may trigger an alert sound. It is important to note that, at this time, screen readers do not inform users of alerts that are present on the page before page load completes.

    Because alerts are intended to provide important and potentially time-sensitive information without interfering with the user's ability to continue working, it is crucial they do not affect keyboard focus. The alert dialogarrow-up-right is designed for situations where interrupting work flow is necessary.

    It is also important to avoid designing alerts that disappear automatically. An alert that disappears too quickly can lead to failure to meet WCAG 2.0 success criterion 2.2.3arrow-up-right. Another critical design consideration is the frequency of interruption caused by alerts. Frequent interruptions inhibit usability for people with visual and cognitive disabilities, which makes meeting the requirements of WCAG 2.0 success criterion 2.2.4arrow-up-right more difficult.

    Example

    Alert Examplearrow-up-right

    Keyboard Interaction

    An alert (WAI-ARIA live region) does not require any keyboard interaction.

    WAI-ARIA Roles, States, and Properties

    The widget has a role of alertarrow-up-right.

    hashtag
    3.3 Alert and Message Dialogs

    An alert dialog is a modal dialogarrow-up-right that interrupts the user's workflow to communicate an important message and acquire a response. Examples include action confirmation prompts and error message confirmations. The alertdialogarrow-up-right role enables assistive technologies and browsers to distinguish alert dialogs from other dialogs so they have the option of giving alert dialogs special treatment, such as playing a system alert sound.

    Example

    Alert Dialog Examplearrow-up-right: A confirmation prompt that demonstrates an alert dialog.

    Keyboard Interaction

    See the keyboard interaction section for the modal dialog patternarrow-up-right.

    WAI-ARIA Roles, States, and Properties

    • The element that contains all elements of the dialog, including the alert message and any dialog buttons, has role alertdialogarrow-up-right.

    • The element with role alertdialog has either:

      • A value for aria-labelledbyarrow-up-right that refers to the element containing the title of the dialog if the dialog has a visible label.

      • A value for if the dialog does not have a visible label.

    • The element with role alertdialog has a value set for that refers to the element containing the alert message.

    hashtag
    3.4 Breadcrumb

    A breadcrumb trail consists of a list of links to the parent pages of the current page in hierarchical order. It helps users find their place within a website or web application. Breadcrumbs are often placed horizontally before a page's main content.

    Example

    Breadcrumb design pattern examplearrow-up-right

    Keyboard Interaction

    Not applicable.

    WAI-ARIA Roles, States, and Properties

    • Breadcrumb trail is contained within a navigation landmark region.

    • The landmark region is labelled via aria-labelarrow-up-right or aria-labelledbyarrow-up-right.

    • The link to the current page has aria-currentarrow-up-right set to page. If the element representing the current page is not a link, aria-current is optional.

    hashtag
    3.5 Button

    A buttonarrow-up-right is a widget that enables users to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or performing a delete operation. A common convention for informing users that a button launches a dialog is to append "…" (ellipsis) to the button label, e.g., "Save as…".

    In addition to the ordinary button widget, WAI-ARIA supports 2 other types of buttons:

    • Toggle button: A two-state button that can be either off (not pressed) or on (pressed). To tell assistive technologies that a button is a toggle button, specify a value for the attribute aria-pressedarrow-up-right. For example, a button labelled mute in an audio player could indicate that sound is muted by setting the pressed state true. Important: it is critical the label on a toggle does not change when its state changes. In this example, when the pressed state is true, the label remains "Mute" so a screen reader would say something like "Mute toggle button pressed". Alternatively, if the design were to call for the button label to change from "Mute" to "Unmute," the aria-pressed attribute would not be needed.

    • Menu button: as described in the menu button patternarrow-up-right, a button is revealed to assistive technologies as a menu button if it has the property aria-haspopuparrow-up-right set to either menu or true.

    Note

    The types of actions performed by buttons are distinctly different from the function of a link (see link patternarrow-up-right). It is important that both the appearance and role of a widget match the function it provides. Nevertheless, elements occasionally have the visual style of a link but perform the action of a button. In such cases, giving the element role button helps assistive technology users understand the function of the element. However, a better solution is to adjust the visual design so it matches the function and ARIA role.

    Examples

    Button Examplesarrow-up-right: Examples of clickable HTML div and span elements made into accessible command and toggle buttons.

    Keyboard Interaction

    When the button has focus:

    • Space: Activates the button.

    • Enter: Activates the button.

    • Following button activation, focus is set depending on the type of action the button performs. For example:

      • If activating the button opens a dialog, the focus moves inside the dialog. (see )

      • If activating the button closes a dialog, focus typically returns to the button that opened the dialog unless the function performed in the dialog context logically leads to a different element. For example, activating a cancel button in a dialog returns focus to the button that opened the dialog. However, if the dialog were confirming the action of deleting the page from which it was opened, the focus would logically move to a new context.

      • If activating the button does not dismiss the current context, then focus typically remains on the button after activation, e.g., an Apply or Recalculate button.

      • If the button action indicates a context change, such as move to next step in a wizard or add another search criteria, then it is often appropriate to move focus to the starting point for that action.

      • If the button is activated with a shortcut key, the focus usually remains in the context from which the shortcut key was activated. For example, if Alt + U were assigned to an "Up" button that moves the currently focused item in a list one position higher in the list, pressing Alt + U when the focus is in the list would not move the focus from the list.

    WAI-ARIA Roles, States, and Properties

    • The button has role of buttonarrow-up-right.

    • The button has an accessible label. By default, the accessible name is computed from any text content inside the button element. However, it can also be provided with aria-labelledbyarrow-up-right or aria-labelarrow-up-right.

    • If a description of the button's function is present, the button element has set to the ID of the element containing the description.

    • When the action associated with a button is unavailable, the button has set to true.

    • If the button is a toggle button, it has an state. When the button is toggled on, the value of this state is true, and when toggled off, the state is false.

    hashtag
    3.6 Carousel (Slide Show or Image Rotator)

    A carousel presents a set of items, referred to as slides, by sequentially displaying a subset of one or more slides. Typically, one slide is displayed at a time, and users can activate a next or previous slide control that hides the current slide and "rotates" the next or previous slide into view. In some implementations, rotation automatically starts when the page loads, and it may also automatically stop once all the slides have been displayed. While a slide may contain any type of content, image carousels where each slide contains nothing more than a single image are common.

    Ensuring all users can easily control and are not adversely effected by slide rotation is an essential aspect of making carousels accessible. For instance, the screen reader experience can be confusing and disorienting if slides that are not visible on screen are incorrectly hidden, e.g., displayed off-screen. Similarly, if slides rotate automatically and a screen reader user is not aware of the rotation, the user may read an element on slide one, execute the screen reader command for next element, and, instead of hearing the next element on slide one, hear an element from slide 2 without any knowledge that the element just announced is from an entirely new context.

    Features needed to provide sufficient rotation control include:

    • Buttons for displaying the previous and next slides.

    • Optionally, a control, or group of controls, for choosing a specific slide to display. For example, slide picker controls can be marked up as tabs in a tablist with the slide represented by a tabpanel element.

    • If the carousel can automatically rotate, it also:

      • Has a button for stopping and restarting rotation. This is particularly important for supporting assistive technologies operating in a mode that does not move either keyboard focus or the mouse.

      • Stops rotating when keyboard focus enters the carousel. It does not restart unless the user explicitly requests it to do so.

      • Stops rotating whenever the mouse is hovering over the carousel.

    Example

    Auto-Rotating Image Carousel Example:arrow-up-right A basic image carousel that demonstrates the accessibility features necessary for carousels that rotate automatically on page load.

    Terms

    The following terms are used to describe components of a carousel.

    Slide

    A single content container within a set of content containers that hold the content to be presented by the carousel.

    Rotation Control

    An interactive element that stops and starts automatic slide rotation.

    Next Slide Control

    An interactive element, often styled as an arrow, that displays the next slide in the rotation sequence.

    Previous Slide Control

    An interactive element, often styled as an arrow, that displays the previous slide in the rotation sequence.

    Slide Picker Controls

    A group of elements, often styled as small dots, that enable the user to pick a specific slide in the rotation sequence to display.

    Keyboard Interaction

    • If the carousel has an auto-rotate feature, automatic slide rotation stops when any element in the carousel receives keyboard focus. It does not resume unless the user activates the rotation control.

    • Tab and Shift + Tab: Move focus through the interactive elements of the carousel as specified by the page tab sequence -- scripting for Tab is not necessary.

    • Button elements implement the keyboard interaction defined in the button patternarrow-up-right. Note: Activating the rotation control, next slide, and previous slide do not move focus, so users may easily repetitively activate them as many times as desired.

    • If present, the rotation control is the first element in the Tab sequence inside the carousel. It is essential that it precede the rotating content so it can be easily located.

    • If tab elements are used for slide picker controls, they implement the keyboard interaction defined in the

    WAI-ARIA Roles, States, and Properties

    This section describes the element composition for three styles of carousels:

    • Basic: Has rotation, previous slide, and next slide controls but no slide picker controls.

    • Tabbed: Has basic controls plus a single tab stop for slide picker controls implemented using the tabs pattern.arrow-up-right

    • Grouped: Has basic controls plus a series of tab stops in a group of slide picker controls where each control implements the button pattern.arrow-up-right Because each slide selector button adds an element to the page tab sequence, this style is the least friendly for keyboard users.

    Basic carousel elements

    • A carousel container element that encompasses all components of the carousel, including both carousel controls and slides, has either role regionarrow-up-right or role group.arrow-up-right The most appropriate role for the carousel container depends on the information architecture of the page. See the landmark regions guidancearrow-up-right to determine whether the carousel warrants being designated as a landmark region.

    • The carousel container has the aria-roledescriptionarrow-up-right property set to carousel.

    • If the carousel has a visible label, its accessible label is provided by the property on the carousel container set to the ID of the element containing the visible label. Otherwise, an accessible label is provided by the property set on the carousel container. Note that since the aria-roledescription is set to "carousel", the label does not contain the word "carousel".

    • The rotation control, next slide control, and previous slide control are either native button elements (recommended) or implement the .

    • The rotation control has an accessible label provided by either its inner text or . The label changes to match the action the button will perform, e.g., "Stop slide rotation" or "Start slide rotation". A label that changes when the button is activated clearly communicates both that slide content can change automatically and when it is doing so. Note that since the label changes, the rotation control does not have any states, e.g., aria-pressed, specified.

    • Each slide container has role with the property set to slide.

    • Each slide has an accessible name:

      • If a slide has a visible label, its accessible label is provided by the property on the slide container set to the ID of the element containing the visible label.

      • Otherwise, an accessible label is provided by the property set on the slide container.

    • Optionally, an element wrapping the set of slide elements has set to false and set to:

      • off: if the carousel is automatically rotating.

    Tabbed Carousel Elements

    The structure of a tabbed carousel is the same as a basic carousel except that:

    • Each slide container has role tabpanelarrow-up-right in lieu of group, and it does not have the aria-roledescription property.

    • It has slide picker controls implemented using the tabs patternarrow-up-right where:

      • Each control is a tab element, so activating a tab displays the slide associated with that tab.

      • The accessible name of each tab indicates which slide it will display by including the name or number of the slide, e.g., "Slide 3". Slide names are preferable if each slide has a unique name.

      • The set of controls is grouped in a tablist element with an accessible name provided by the value of that identifies the purpose of the tabs, e.g., "Choose slide to display."

      • The tab, tablist, and tabpanel implement the properties specified in the .

    Grouped Carousel Elements

    A grouped carousel has the same structure as a basic carousel, but it also includes slide picker controls where:

    • The set of slide picker controls is contained in an element with role grouparrow-up-right.

    • The group containing the picker controls has an accessible label provided by the value of aria-labelarrow-up-right that identifies the purpose of the controls, e.g., "Choose slide to display."

    • Each picker control is a native button element (recommended) or implements the button pattern.arrow-up-right

    • The accessible name of each picker button matches the name of the slide it displays. One technique for accomplishing this is to set to a value that references the slide group element.

    • The picker button representing the currently displayed slide has the property set to true. Note: aria-disabled is preferable to the HTML disabled attribute because this is a circumstance where screen reader users benefit from the disabled button being included in the page Tab sequence.

    hashtag
    3.7 Checkbox

    WAI-ARIA supports two types of checkboxarrow-up-right widgets:

    1. Dual-state: The most common type of checkbox, it allows the user to toggle between two choices -- checked and not checked.

    2. Tri-state: This type of checkbox supports an additional third state known as partially checked.

    One common use of a tri-state checkbox can be found in software installers where a single tri-state checkbox is used to represent and control the state of an entire group of install options. And, each option in the group can be individually turned on or off with a dual state checkbox.

    • If all options in the group are checked, the overall state is represented by the tri-state checkbox displaying as checked.

    • If some of the options in the group are checked, the overall state is represented with the tri-state checkbox displaying as partially checked.

    • If none of the options in the group are checked, the overall state of the group is represented with the tri-state checkbox displaying as not checked.

    The user can use the tri-state checkbox to change all options in the group with a single action:

    • Checking the overall checkbox checks all options in the group.

    • Unchecking the overall checkbox will uncheck all options in the group.

    • And, In some implementations, the system may remember which options were checked the last time the overall status was partially checked. If this feature is provided, activating the overall checkbox a third time recreates that partially checked state where only some options in the group are checked.

    Examples

    • Simple Two-State Checkbox Examplearrow-up-right: Demonstrates a simple 2-state checkbox.

    • Tri-State Checkbox Examplearrow-up-right: Demonstrates how to make a widget that uses the mixed value for aria-checked and group collection of checkboxes with a field set.

    Keyboard Interaction

    When the checkbox has focus, pressing the Space key changes the state of the checkbox.

    WAI-ARIA Roles, States, and Properties

    • The checkbox has role checkboxarrow-up-right.

    • The checkbox has an accessible label provided by one of the following:

      • Visible text content contained within the element with role checkbox.

      • A visible label referenced by the value of set on the element with role checkbox.

      • set on the element with role checkbox.

    • When checked, the checkbox element has state set to true.

    • When not checked, it has state set to false.

    • When partially checked, it has state set to mixed.

    • If a set of checkboxes is presented as a logical group with a visible label, the checkboxes are included in an element with role that has the property set to the ID of the element containing the label.

    • If the presentation includes additional descriptive static text relevant to a checkbox or checkbox group, the checkbox or checkbox group has the property set to the ID of the element containing the description.

    hashtag
    3.8 Combo Box

    A comboboxarrow-up-right is a widget made up of the combination of two distinct elements: 1) a single-line textbox, and 2) an associated pop-up element for helping users set the value of the textbox. The popup may be a listboxarrow-up-right, gridarrow-up-right, treearrow-up-right, or dialog.arrow-up-right Many implementations also include a third optional element -- a graphical button adjacent to the textbox, indicating the availability of the popup. Activating the button displays the popup if suggestions are available.

    The popup is hidden by default, and the conditions that trigger its display are specific to each implementation. Some possible popup display conditions include:

    • It is displayed only if a certain number of characters are typed in the textbox and those characters match some portion of one of the suggested values.

    • It is displayed as soon as the textbox is focused, even if the textbox is empty.

    • It is displayed when the Down Arrow key is pressed or the show button is activated, possibly with a dependency on the content of the textbox.

    • It is displayed if the value of the textbox is altered in a way that creates one or more partial matches to a suggested value.

    Combobox widgets are useful for setting the value of a single-line textbox in one of two types of scenarios:

    1. The value for the textbox must be chosen from a predefined set of allowed values, e.g., a location field must contain a valid location name. Note that the listbox and menu button patterns are also useful in this scenario; differences between combobox and alternative patterns are described below.

    2. The textbox may contain any arbitrary value, but it is advantageous to suggest possible values to the user, e.g., a search field may suggest similar or previous searches to save the user time.

    The nature of the suggested values and the way the suggestions are presented is called the autocomplete behavior. Comboboxes can have one of four forms of autocomplete:

    1. No autocomplete: When the popup is triggered, the suggested values it contains are the same regardless of the characters typed in the textbox. For example, the popup suggests a set of recently entered values, and the suggestions do not change as the user types.

    2. List autocomplete with manual selection: When the popup is triggered, it presents suggested values that complete or logically correspond to the characters typed in the textbox. The character string the user has typed will become the value of the textbox unless the user selects a value in the popup.

    3. List autocomplete with automatic selection: When the popup is triggered, it presents suggested values that complete or logically correspond to the characters typed in the textbox, and the first suggestion is automatically highlighted as selected. The automatically selected suggestion becomes the value of the textbox when the combobox loses focus unless the user chooses a different suggestion or changes the character string in the textbox.

    4. List with inline autocomplete: This is the same as list with automatic selection with one additional feature. The portion of the selected suggestion that has not been typed by the user, a completion string, appears inline after the input cursor in the textbox. The inline completion string is visually highlighted and has a selected state.

    With any form of list autocomplete, the popup may appear and disappear as the user types. For example, if the user types a two character string that triggers five suggestions to be displayed but then types a third character that forms a string that does not have any matching suggestions, the popup may close and, if present, the inline completion string disappears.

    When constructing a widget that is both visually compact and enables users to choose one value from a set of discrete values, often either a listboxarrow-up-right or menu buttonarrow-up-right is simpler to implement and use. One feature of combobox that distinguishes it from both listbox and menu button is that the value of the combobox is presented in an edit field. Thus, the combobox gives users one function that both listbox and menu button lack, namely the ability to select some or all of the value for copying to the clipboard. One feature that distinguishes both combobox and menu button widgets from listbox widgets is their ability to provide an undo mechanism. In many implementations, users can navigate the set of allowed values in a combobox or menu and then decide to revert to the value the widget had before navigating by pressing escape. In contrast, navigating a listbox immediately changes its value, and escape does not provide an undo mechanism.

    Note

    The options for a combobox to popup a grid, tree, or dialog were introduced in ARIA 1.1. Changes made in the ARIA 1.1 specification also add support for a code pattern that enables assistive technologies to present the textbox and popup as separately perceivable elements. both ARIA 1.0 and 1.1 patterns are described in the following sections. While using the ARIA 1.1 pattern is recommended as soon as assistive technology support is sufficient, there are no plans to deprecate the ARIA 1.0 pattern.

    Examples

    • Examples of ARIA 1.1 Combobox with Listbox Popuparrow-up-right: Comboboxes that demonstrate the various forms of autocomplete behavior using a listbox popup and use the ARIA 1.1 implementation pattern.

    • Example of ARIA 1.1 Combobox with Grid Popuparrow-up-right: A combobox that presents suggestions in a grid, enabling users to navigate descriptive information about each suggestion.

    • ARIA 1.0 Combobox with Both List and Inline Autocompletearrow-up-right: A combobox that demonstrates the autocomplete behavior known as list with inline autocomplete and uses the ARIA 1.0 implementation pattern.

    • : A combobox that demonstrates the autocomplete behavior known as list with manual selection and uses the ARIA 1.0 implementation pattern.

    • : A combo box that demonstrates the behavior associated with aria-autocomplete=none and uses the ARIA 1.0 implementation pattern.

    Keyboard Interaction

    • Tab: The textbox is in the page Tab sequence.

    • Note: The popup indicator icon or button (if present), the popup, and the popup descendants are excluded from the page Tab sequence.

    Textbox Keyboard Interaction

    When focus is in the textbox:

    • Down Arrow: If the popup is available, moves focus into the popup:

      • If the autocomplete behavior automatically selected a suggestion before Down Arrow was pressed, focus is placed on the suggestion following the automatically selected suggestion.

      • Otherwise, places focus on the first focusable element in the popup.

    • Up Arrow (Optional): If the popup is available, places focus on the last focusable element in the popup.

    • Escape: Dismisses the popup if it is visible. Optionally, clears the textbox.

    • Enter: If an autocomplete suggestion is automatically selected, accepts the suggestion either by placing the input cursor at the end of the accepted value in the textbox or by performing a default action on the value. For example, in a messaging application, the default action may be to add the accepted value to a list of message recipients and then clear the textbox so the user can add another recipient.

    • Printable Characters: Type characters in the textbox. Note that some implementations may regard certain characters as invalid and prevent their input.

    • Standard single line text editing keys appropriate for the device platform (see note below).

    • Alt + Down Arrow (Optional): If the popup is available but not displayed, displays the popup without moving focus.

    • Alt + Up Arrow (Optional): If the popup is displayed:

      1. If the popup contains focus, returns focus to the textbox.

      2. Closes the popup.

    Note

    Standard single line text editing keys appropriate for the device platform:

    1. include keys for input, cursor movement, selection, and text manipulation.

    2. Standard key assignments for editing functions depend on the device operating system.

    3. The most robust approach for providing text editing functions is to rely on browsers, which supply them for HTML inputs with type text and for elements with the contenteditable HTML attribute.

    4. IMPORTANT: Be sure that JavaScript does not interfere with browser-provided text editing functions by capturing key events for the keys used to perform them.

    When focus is in a listbox popup:

    • Enter: Accepts the focused option in the listbox by closing the popup and placing the accepted value in the textbox with the input cursor at the end of the value.

    • Escape: Closes the popup and returns focus to the textbox. Optionally, clears the contents of the textbox.

    • Right Arrow: Returns focus to the textbox without closing the popup and moves the input cursor one character to the right. If the input cursor is on the right-most character, the cursor does not move.

    • Left Arrow: Returns focus to the textbox without closing the popup and moves the input cursor one character to the left. If the input cursor is on the left-most character, the cursor does not move.

    • Any printable character: Returns the focus to the textbox without closing the popup and types the character.

    • Backspace (Optional): Returns focus to the textbox and deletes the character prior to the cursor.

    • Delete (Optional): Returns focus to the textbox, removes the selected state if a suggestion was selected, and removes the inline autocomplete string if present.

    • Down Arrow: Moves focus to and selects the next option. If focus is on the last option, either returns focus to the textbox or does nothing.

    • Up Arrow: Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the textbox or does nothing.

    • Home (Optional): Either moves focus to and selects the first option or returns focus to the textbox and places the cursor on the first character.

    • End (Optional): Either moves focus to the last option or returns focus to the textbox and places the cursor after the last character.

    Note

    1. DOM Focus is maintained on the combobox textbox and the assistive technology focus is moved within the listbox using aria-activedescendant as described in Managing Focus in Composites Using aria-activedescendant.arrow-up-right

    2. Selection follows focus in the listbox; the listbox allows only one suggested value to be selected at a time for the textbox value.

    In a grid popup, each suggested value may be represented by either a single cell or an entire row. See notes below for how this aspect of grid design effects the keyboard interaction design and the way that selection moves in response to focus movements.

    • Enter: Accepts the currently selected suggested value by closing the popup and placing the selected value in the textbox with the input cursor at the end of the value.

    • Escape: Closes the popup and returns focus to the textbox. Optionally, clears the contents of the textbox.

    • Any printable character: Returns the focus to the textbox without closing the popup and types the character.

    • Backspace (Optional): Returns focus to the textbox and deletes the character prior to the cursor.

    • Delete (Optional): Returns focus to the textbox, removes the selected state if a suggestion was selected, and removes the inline autocomplete string if present.

    • Right Arrow: Moves focus one cell to the right. Optionally, if focus is on the right-most cell in the row, focus may move to the first cell in the following row. If focus is on the last cell in the grid, either does nothing or returns focus to the textbox.

    • Left Arrow: Moves focus one cell to the left. Optionally, if focus is on the left-most cell in the row, focus may move to the last cell in the previous row. If focus is on the first cell in the grid, either does nothing or returns focus to the textbox.

    • Down Arrow: Moves focus one cell down. If focus is in the last row of the grid, either does nothing or returns focus to the textbox.

    • Up Arrow: Moves focus one cell up. If focus is in the first row of the grid, either does nothing or returns focus to the textbox.

    • Page Down (Optional): Moves focus down an author-determined number of rows, typically scrolling so the bottom row in the currently visible set of rows becomes one of the first visible rows. If focus is in the last row of the grid, focus does not move.

    • Page Up (Optional): Moves focus up an author-determined number of rows, typically scrolling so the top row in the currently visible set of rows becomes one of the last visible rows. If focus is in the first row of the grid, focus does not move.

    • Home (Optional): Either:

      • Moves focus to the first cell in the row that contains focus. Or, if the grid has fewer than three cells per row or multiple suggested values per row, focus may move to the first cell in the grid.

      • Returns focus to the textbox and places the cursor on the first character.

    • End (Optional): Either:

      • Moves focus to the last cell in the row that contains focus. Or, if the grid has fewer than three cells per row or multiple suggested values per row, focus may move to the last cell in the grid.

      • Returns focus to the textbox and places the cursor after the last character.

    • Control + Home (optional): moves focus to the first row.

    • Control + End (Optional): moves focus to the last row.

    Note

    1. DOM Focus is maintained on the combobox textbox and the assistive technology focus is moved within the grid using aria-activedescendant as described in Managing Focus in Composites Using aria-activedescendant.arrow-up-right

    2. The grid allows only one suggested value to be selected at a time for the textbox value.

    3. In a grid popup, each suggested value may be represented by either a single cell or an entire row. This aspect of design effects focus and selection movement:

      1. If every cell contains a different suggested value:

        • Selection follows focus so that the cell containing focus is selected.

        • Horizontal arrow key navigation typically wraps from one row to another.

    In some implementations of tree popups, some or all parent nodes may serve as suggestion category labels so may not be selectable values. See notes below for how this aspect of the design effects the way selection moves in response to focus movements.

    When focus is in a vertically oriented tree popup:

    • Enter: Accepts the currently selected suggested value by closing the popup and placing the selected value in the textbox with the input cursor at the end of the value.

    • Escape: Closes the popup and returns focus to the textbox. Optionally, clears the contents of the textbox.

    • Any printable character: Returns the focus to the textbox without closing the popup and types the character.

    • Right arrow:

      • When focus is on a closed node, opens the node; focus and selection do not move.

      • When focus is on a open node, moves focus to the first child node and selects it if it is selectable.

    • Left arrow:

      • When focus is on an open node, closes the node.

      • When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node and selects it if it is selectable.

    • Down Arrow: Moves focus to the next node that is focusable without opening or closing a node and selects it if it is selectable.

    • Up Arrow: Moves focus to the previous node that is focusable without opening or closing a node and selects it if it is selectable.

    • Home: Moves focus to the first node in the tree without opening or closing a node and selects it if it is selectable.

    • End: Moves focus to the last node in the tree that is focusable without opening a node and selects it if it is selectable.

    Note

    1. DOM Focus is maintained on the combobox textbox and the assistive technology focus is moved within the tree using aria-activedescendant as described in Managing Focus in Composites Using aria-activedescendant.arrow-up-right

    2. The tree allows only one suggested value to be selected at a time for the textbox value.

    3. In a tree popup, some or all parent nodes may not be selectable values; they may serve as category labels for suggested values. If focus moves to a node that is not a selectable value, either:

      • The previously selected node, if any, remains selected until focus moves to a node that is selectable.

      • There is no selected value.

      • In either case, focus is visually distinct from selection so users can readily see if a value is selected or not.

    4. If the nodes in a tree are arranged horizontally:

      1. Down Arrow performs as Right Arrow is described above, and vice versa.

      2. Up Arrow performs as Left Arrow is described above, and vice versa.

    When focus is in a dialog popup:

    • There are two ways to close the popup and return focus to the textbox:

      1. Perform an action in the dialog, such as activate a button, that specifies a value for the textbox.

      2. Cancel out of the dialog, e.g., press Escape or activate the cancel button in the dialog. Canceling either returns focus to the text box without changing the textbox value or returns focus to the textbox and clears the textbox.

    • The dialog implements the keyboard interaction defined in the

    Note

    Unlike other combobox popups, dialogs do not support aria-activedescendant so DOM focus moves into the dialog from the textbox.

    WAI-ARIA Roles, States, and Properties

    The role, state, and property guidance where the ARIA 1.1 and ARIA 1.0 patterns differ is listed first. The subsequent guidance applies to both patterns.

    • In a combobox implementing the ARIA 1.1 pattern:

      • The element that serves as the combobox container has role comboboxarrow-up-right.

      • The element with role combobox contains or owns a textbox element that has either role or role .

      • When the combobox popup is visible, the combobox element contains or owns an element that has role , , , or .

      • If the combobox popup has a role other than listbox, the element with role combobox has set to a value that corresponds to the popup type. That is, aria-haspopup is set to grid, tree, or dialog. Note that elements with role combobox have an implicit aria-haspopup value of listbox.

      • When the combobox popup is visible, the textbox element has set to a value that refers to the combobox popup element.

    • In a combobox implementing the ARIA 1.0 pattern:

      • The element that serves as the textbox has role .

      • When the combobox popup is visible, the element with role combobox has set to a value that refers to an element with role .

    • The textbox element has a value for of false. Note that the default value of aria-multiline is false.

    • When the combobox popup is not visible, the element with role combobox has set to false. When the popup element is visible, aria-expanded is set to true. Note that elements with role combobox have a default value for aria-expanded of false.

    • When a combobox receives focus, DOM focus is placed on the textbox element.

    • When a descendant of a listbox, grid, or tree popup is focused, DOM focus remains on the textbox and the textbox has set to a value that refers to the focused element within the popup.

    • In a combobox with a listbox, grid, or tree popup, when a suggested value is visually indicated as the currently selected value, the option, gridcell, row, or treeitem containing that value has set to true.

    • If the combobox has a visible label, the element with role combobox has set to a value that refers to the labelling element. Otherwise, the combobox element has a label provided by .

    • The textbox element has set to a value that corresponds to its autocomplete behavior:

      • none: When the popup is displayed, the suggested values it contains are the same regardless of the characters typed in the textbox.

      • list

    Note

    1. When referring to the roles, states, and properties documentation for the below list of patterns used for popups, keep in mind that a combobox is a single-select widget where selection always follows focus in the popup.

    2. The roles, states, and properties for popup elements are defined in their respective design patterns:

      • Listbox Roles, States, and Propertiesarrow-up-right

    hashtag
    3.9 Dialog (Modal)

    A dialogarrow-up-right is a window overlaid on either the primary window or another dialog window. Windows under a modal dialog are inert. That is, users cannot interact with content outside an active dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so it is difficult to discern, and in some implementations, attempts to interact with the inert content cause the dialog to close.

    Like non-modal dialogs, modal dialogs contain their tab sequence. That is, Tab and Shift + Tab do not move focus outside the dialog. However, unlike most non-modal dialogs, modal dialogs do not provide means for moving keyboard focus outside the dialog window without closing the dialog.

    The alertdialogarrow-up-right role is a special-case dialog role designed specifically for dialogs that divert users' attention to a brief, important message. Its usage is described in the alert dialog design pattern.arrow-up-right

    Examples

    • Modal Dialog Examplearrow-up-right: Demonstrates multiple layers of modal dialogs with both small and large amounts of content.

    • Date Picker Dialog Examplearrow-up-right: Demonstrates a dialog containing a calendar grid for choosing a date.

    Keyboard Interaction

    In the following description, the term tabbable element refers to any element with a tabindex value of zero or greater. Note that values greater than 0 are strongly discouraged.

    • When a dialog opens, focus moves to an element inside the dialog. See notes below regarding initial focus placement.

    • Tab:

      • Moves focus to the next tabbable element inside the dialog.

      • If focus is on the last tabbable element inside the dialog, moves focus to the first tabbable element inside the dialog.

    • Shift + Tab:

      • Moves focus to the previous tabbable element inside the dialog.

      • If focus is on the first tabbable element inside the dialog, moves focus to the last tabbable element inside the dialog.

    • Escape: Closes the dialog.

    Note

    1. When a dialog opens, focus placement depends on the nature and size of the content.

      • In all circumstances, focus moves to an element contained in the dialog.

      • Unless a condition where doing otherwise is advisable, focus is initially set on the first focusable element.

      • If content is large enough that focusing the first interactive element could cause the beginning of content to scroll out of view, it is advisable to add tabindex=-1 to a static element at the top of the dialog, such as the dialog title or first paragraph, and initially focus that element.

      • If a dialog contains the final step in a process that is not easily reversible, such as deleting data or completing a financial transaction, it may be advisable to set focus on the least destructive action, especially if undoing the action is difficult or impossible. The is often employed in such circumstances.

      • If a dialog is limited to interactions that either provide additional information or continue processing, it may be advisable to set focus to the element that is likely to be most frequently used, such as an OK or Continue button.

    2. When a dialog closes, focus returns to the element that invoked the dialog unless either:

      • The invoking element no longer exists. Then, focus is set on another element that provides logical work flow.

      • The work flow design includes the following conditions that can occasionally make focusing a different element a more logical choice:

    3. It is strongly recommended that the tab sequence of all dialogs include a visible element with role button that closes the dialog, such as a close icon or cancel button.

    WAI-ARIA Roles, States, and Properties

    • The element that serves as the dialog container has a role of dialogarrow-up-right.

    • All elements required to operate the dialog are descendants of the element that has role dialog.

    • The dialog container element has aria-modalarrow-up-right set to true.

    • The dialog has either:

      • A value set for the property that refers to a visible dialog title.

      • A label specified by .

    • Optionally, the property is set on the element with the dialog role to indicate which element or elements in the dialog contain content that describes the primary purpose or message of the dialog. Specifying descriptive elements enables screen readers to announce the description along with the dialog title and initially focused element when the dialog opens.

    Note

    • Because marking a dialog modal by setting aria-modalarrow-up-right to true can prevent users of some assistive technologies from perceiving content outside the dialog, users of those technologies will experience severe negative ramifications if a dialog is marked modal but does not behave as a modal for other users. So, mark a dialog modal only when both:

      1. Application code prevents all users from interacting in any way with content outside of it.

      2. Visual styling obscures the content outside of it.

    • The aria-modal property introduced by ARIA 1.1 replaces for informing assistive technologies that content outside a dialog is inert. However, in legacy dialog implementations where aria-hidden is used to make content outside a dialog inert for assistive technology users, it is important that:

      1. aria-hidden is set to true on each element containing a portion of the inert layer.

    hashtag
    3.10 Disclosure (Show/Hide)

    A disclosure is a buttonarrow-up-right that controls visibility of a section of content. When the controlled content is hidden, it is often styled as a typical push button with a right-pointing arrow or triangle to hint that activating the button will display additional content. When the content is visible, the arrow or triangle typically points down.

    Examples

    • Disclosure (Show/Hide) of Image Descriptionarrow-up-right

    • Disclosure (Show/Hide) of Answers to Frequently Asked Questionsarrow-up-right

    • Disclosure (Show/Hide) for Navigation Menusarrow-up-right

    Keyboard Interaction

    When the disclosure control has focus:

    • Enter: activates the disclosure control and toggles the visibility of the disclosure content.

    • Space: activates the disclosure control and toggles the visibility of the disclosure content.

    WAI-ARIA Roles, States, and Properties

    • The element that shows and hides the content has role buttonarrow-up-right.

    • When the content is visible, the element with role button has aria-expandedarrow-up-right set to true. When the content area is hidden, it is set to false.

    • Optionally, the element with role button has a value specified for that refers to the element that contains all the content that is shown or hidden.

    hashtag
    3.11 Feed

    A feedarrow-up-right is a section of a page that automatically loads new sections of content as the user scrolls. The sections of content in a feed are presented in articlearrow-up-right elements. So, a feed can be thought of as a dynamic list of articles that often appears to scroll infinitely.

    The feature that most distinguishes feed from other ARIA patterns that support loading data as users scroll, e.g., a gridarrow-up-right, is that a feed is a structure, not a widget. Consequently, assistive technologies with a reading mode, such as screen readers, default to reading mode when interacting with feed content. However, unlike most other WAI-ARIA structures, a feed establishes an interoperability contract between the web page and assistive technologies. The contract governs scroll interactions so that assistive technology users can read articles, jump forward and backward by article, and reliably trigger new articles to load while in reading mode.

    For example, a product page on a shopping site may have a related products section that displays five products at a time. As the user scrolls, more products are requested and loaded into the DOM. While a static design might include a next button for loading five more products, a dynamic implementation that automatically loads more data as the user scrolls simplifies the user experience and reduces the inertia associated with viewing more than the first five product suggestions. But, unfortunately when web pages load content dynamically based on scroll events, it can cause usability and interoperability difficulties for users of assistive technologies.

    The feed pattern enables reliable assistive technology reading mode interaction by establishing the following interoperability agreement between the web page and assistive technologies:

    1. In the context of a feed, the web page code is responsible for:

      • Appropriate visual scrolling of the content based on which article contains DOM focus.

      • Loading or removing feed articles based on which article contains DOM focus.

    2. In the context of a feed, assistive technologies with a reading mode are responsible for:

      • Indicating which article contains the reading cursor by ensuring the article element or one of its descendants has DOM focus.

      • providing reading mode keys that move DOM focus to the next and previous articles.

    Thus, implementing the feed pattern allows a screen reader to reliably read and trigger the loading of feed content while staying in its reading mode.

    Another feature of the feed pattern is its ability to facilitate skim reading for assistive technology users. Web page authors may provide both an accessible name and description for each article. By identifying the elements inside of an article that provide the title and the primary content, assistive technologies can provide functions that enable users to jump from article to article and efficiently discern which articles may be worthy of more attention.

    Example

    Example Implementation of Feed Patternarrow-up-right

    Keyboard Interaction

    The feed pattern is not based on a desktop GUI widget so the feed role is not associated with any well-established keyboard conventions. Supporting the following, or a similar, interface is recommended.

    When focus is inside the feed:

    • Page Down: Move focus to next article.

    • Page Up: Move focus to previous article.

    • Control + End: Move focus to the first focusable element after the feed.

    • Control + Home: Move focus to the first focusable element before the feed.

    Note

    1. Due to the lack of convention, providing easily discoverable keyboard interface documentation is especially important.

    2. In some cases, a feed may contain a nested feed. For example, an article in a social media feed may contain a feed of comments on that article. To navigate the nested feed, users first move focus inside the nested feed. Options for supporting nested feed navigation include:

      • Users move focus into the nested feed from the content of the containing article with Tab. This may be slow if the article contains a significant number of links, buttons, or other widgets.

      • Provide a key for moving focus from the elements in the containing article to the first item in the nested feed, e.g., Alt + Page Down.

      • To continue reading the outer feed, Control + End moves focus to the next article in the outer feed.

    3. In the rare circumstance that a feed article contains a widget that uses the above suggested keys, the feed navigation key will operate the contained widget, and the user needs to move focus to an element that does not utilize the feed navigation keys in order to navigate the feed.

    WAI-ARIA Roles, States, and Properties

    • The element that contains the set of feed articles has role feedarrow-up-right.

    • If the feed has a visible label, the feed element has aria-labelledbyarrow-up-right referring to the element containing the title. Otherwise, the feed element has a label specified with aria-labelarrow-up-right.

    • Each unit of content in a feed is contained in an element with role . All content inside the feed is contained in an article element.

    • Each article element has referring to elements inside the article that can serve as a distinguishing label.

    • It is optional but strongly recommended for each article element to have referring to one or more elements inside the article that serve as the primary content of the article.

    • Each article element has set to a value that represents its position in the feed.

    • Each article element has set to a value that represents either the total number of articles that have been loaded or the total number in the feed, depending on which value is deemed more helpful to users. If the total number in the feed is undetermined, it can be represented by a aria-setsize value of -1.

    • When article elements are being added to or removed from the feed container, and if the operation requires multiple DOM operations, the feed element has set to true during the update operation. Note that it is extremely important that aria-busy is set to false when the operation is complete or the changes may not become visible to some assistive technology users.

    hashtag
    3.12 Grids : Interactive Tabular Data and Layout Containers

    A gridarrow-up-right widget is a container that enables users to navigate the information or interactive elements it contains using directional navigation keys, such as arrow keys, Home, and End. As a generic container widget that offers flexible keyboard navigation, it can serve a wide variety of needs. It can be used for purposes as simple as grouping a collection of checkboxes or navigation links or as complex as creating a full-featured spreadsheet application. While the words "row" and "column" are used in the names of WAI-ARIA attributes and by assistive technologies when describing and presenting the logical structure of elements with the grid role, using the grid role on an element does not necessarily imply that its visual presentation is tabular.

    When presenting content that is tabular, consider the following factors when choosing between implementing this grid pattern or the tablearrow-up-right pattern.

    • A grid is a composite widget so it:

      • Always contains multiple focusable elements.

      • Only one of the focusable elements contained by the grid is included in the page tab sequence.

      • Requires the author to provide code that .

    • All focusable elements contained in a table are included in the page tab sequence.

    Uses of the grid pattern broadly fall into two categories: presenting tabular information (data grids) and grouping other widgets (layout grids). Even though both data grids and layout grids employ the same ARIA roles, states, and properties, differences in their content and purpose surface factors that are important to consider in keyboard interaction design. To address these factors, the following two sections describe separate keyboard interaction patterns for data and layout grids.

    Examples

    • Layout Grid Examplesarrow-up-right: Three example implementations of grids that are used to lay out widgets, including a collection of navigation links, a message recipients list, and a set of search results.

    • Data Grid Examplesarrow-up-right: Three example implementations of grid that include features relevant to presenting tabular information, such as content editing, sort, and column hiding.

    • Advanced Data Grid Examplearrow-up-right: Example of a grid with behaviors and features similar to a typical spreadsheet, including cell and row selection.

    Data Grids For Presenting Tabular Information

    A grid can be used to present tabular information that has column titles, row titles, or both. The grid pattern is particularly useful if the tabular information is editable or interactive. For example, when data elements are links to more information, rather than presenting them in a static table and including the links in the tab sequence, implementing the grid pattern provides users with intuitive and efficient keyboard navigation of the grid contents as well as a shorter tab sequence for the page. A grid may also offer functions, such as cell content editing, selection, cut, copy, and paste.

    In a grid, every cell contains a focusable element or is itself focusable, regardless of whether the cell content is editable or interactive. There is one exception: if column or row header cells do not provide functions, such as sort or filter, they do not need to be focusable. One reason it is important for all cells to be able to receive or contain keyboard focus is that screen readers will typically be in their application reading mode, rather than their document reading mode, when users are interacting with the grid. While in application mode, a screen reader user hears only focusable elements and content that labels focusable elements. So, screen reader users may unknowingly overlook elements contained in a grid that are either not focusable or not used to label a column or row.

    Keyboard Interaction For Data Grids

    The following keys provide grid navigation by moving focus among cells of the grid. Implementations of grid make these key commands available when an element in the grid has received focus, e.g., after a user has moved focus to the grid with Tab.

    • Right Arrow: Moves focus one cell to the right. If focus is on the right-most cell in the row, focus does not move.

    • Left Arrow: Moves focus one cell to the left. If focus is on the left-most cell in the row, focus does not move.

    • Down Arrow: Moves focus one cell down. If focus is on the bottom cell in the column, focus does not move.

    • Up Arrow: Moves focus one cell Up. If focus is on the top cell in the column, focus does not move.

    • Page Down: Moves focus down an author-determined number of rows, typically scrolling so the bottom row in the currently visible set of rows becomes one of the first visible rows. If focus is in the last row of the grid, focus does not move.

    • Page Up: Moves focus up an author-determined number of rows, typically scrolling so the top row in the currently visible set of rows becomes one of the last visible rows. If focus is in the first row of the grid, focus does not move.

    • Home: moves focus to the first cell in the row that contains focus.

    • End: moves focus to the last cell in the row that contains focus.

    • Control + Home: moves focus to the first cell in the first row.

    • Control + End: moves focus to the last cell in the last row.

    Note

    • When the above grid navigation keys move focus, whether the focus is set on an element inside the cell or the grid cell depends on cell content. See Whether to Focus on a Cell or an Element Inside Itarrow-up-right.

    • While navigation keys, such as arrow keys, are moving focus from cell to cell, they are not available to do something like operate a combobox or move an editing caret inside of a cell. If this functionality is needed, see Editing and Navigating Inside a Cellarrow-up-right.

    • If navigation functions can dynamically add more rows or columns to the DOM, key events that move focus to the beginning or end of the grid, such as control + End, may move focus to the last row in the DOM rather than the last available row in the back-end data.

    If a grid supports selection of cells, rows, or columns, the following keys are commonly used for these functions.

    • Control + Space: selects the column that contains the focus.

    • Shift + Space: Selects the row that contains the focus. If the grid includes a column with checkboxes for selecting rows, this key can serve as a shortcut for checking the box when focus is not on the checkbox.

    • Control + A: Selects all cells.

    • Shift + Right Arrow: Extends selection one cell to the right.

    • Shift + Left Arrow: Extends selection one cell to the left.

    • Shift + Down Arrow: Extends selection one cell down.

    • Shift + Up Arrow: Extends selection one cell Up.

    Note

    See § 6.8 Key Assignment Conventions for Common Functionsarrow-up-right for cut, copy, and paste key assignments.

    Layout Grids for Grouping Widgets

    The grid pattern can be used to group a set of interactive elements, such as links, buttons, or checkboxes. Since only one element in the entire grid is included in the tab sequence, grouping with a grid can dramatically reduce the number of tab stops on a page. This is especially valuable if scrolling through a list of elements dynamically loads more of those elements from a large data set, such as in a continuous list of suggested products on a shopping site. If elements in a list like this were in the tab sequence, keyboard users are effectively trapped in the list. If any elements in the group also have associated elements that appear on hover, the grid pattern is also useful for providing keyboard access to those contextual elements of the user interface.

    Unlike grids used to present data, A grid used for layout does not necessarily have header cells for labelling rows or columns and might contain only a single row or a single column. Even if it has multiple rows and columns, it may present a single, logically homogenous set of elements. For example, a list of recipients for a message may be a grid where each cell contains a link that represents a recipient. The grid may initially have a single row but then wrap into multiple rows as recipients are added. In such circumstances, grid navigation keys may also wrap so the user can read the list from beginning to end by pressing either Right Arrow or Down Arrow. While This type of focus movement wrapping can be very helpful in a layout grid, it would be disorienting if used in a data grid, especially for users of assistive technologies.

    Because arrow keys are used to move focus inside of a grid, a grid is both easier to build and use if the components it contains do not require the arrow keys to operate. If a cell contains an element like a listboxarrow-up-right, then an extra key command to focus and activate the listbox is needed as well as a command for restoring the grid navigation functionality. Approaches to supporting this need are described in the section on Editing and Navigating Inside a Cellarrow-up-right.

    Keyboard Interaction For Layout Grids

    The following keys provide grid navigation by moving focus among cells of the grid. Implementations of grid make these key commands available when an element in the grid has received focus, e.g., after a user has moved focus to the grid with Tab.

    • Right Arrow: Moves focus one cell to the right. Optionally, if focus is on the right-most cell in the row, focus may move to the first cell in the following row. If focus is on the last cell in the grid, focus does not move.

    • Left Arrow: Moves focus one cell to the left. Optionally, if focus is on the left-most cell in the row, focus may move to the last cell in the previous row. If focus is on the first cell in the grid, focus does not move.

    • Down Arrow: Moves focus one cell down. Optionally, if focus is on the bottom cell in the column, focus may move to the top cell in the following column. If focus is on the last cell in the grid, focus does not move.

    • Up Arrow: Moves focus one cell up. Optionally, if focus is on the top cell in the column, focus may move to the bottom cell in the previous column. If focus is on the first cell in the grid, focus does not move.

    • Page Down (Optional): Moves focus down an author-determined number of rows, typically scrolling so the bottom row in the currently visible set of rows becomes one of the first visible rows. If focus is in the last row of the grid, focus does not move.

    • Page Up (Optional): Moves focus up an author-determined number of rows, typically scrolling so the top row in the currently visible set of rows becomes one of the last visible rows. If focus is in the first row of the grid, focus does not move.

    • Home: moves focus to the first cell in the row that contains focus. Optionally, if the grid has a single column or fewer than three cells per row, focus may instead move to the first cell in the grid.

    • End: moves focus to the last cell in the row that contains focus. Optionally, if the grid has a single column or fewer than three cells per row, focus may instead move to the last cell in the grid.

    • Control + Home (optional): moves focus to the first cell in the first row.

    • Control + End (Optional): moves focus to the last cell in the last row.

    Note

    • When the above grid navigation keys move focus, whether the focus is set on an element inside the cell or the grid cell depends on cell content. See Whether to Focus on a Cell or an Element Inside Itarrow-up-right.

    • While navigation keys, such as arrow keys, are moving focus from cell to cell, they are not available to do something like operate a combobox or move an editing caret inside of a cell. If this functionality is needed, see Editing and Navigating Inside a Cellarrow-up-right.

    • If navigation functions can dynamically add more rows or columns to the DOM, key events that move focus to the beginning or end of the grid, such as control + End, may move focus to the last row in the DOM rather than the last available row in the back-end data.

    It would be unusual for a layout grid to provide functions that require cell selection. If it did, though, the following keys are commonly used for these functions.

    • Control + Space: selects the column that contains the focus.

    • Shift + Space: Selects the row that contains the focus. If the grid includes a column with checkboxes for selecting rows, this key can serve as a shortcut for checking the box when focus is not on the checkbox.

    • Control + A: Selects all cells.

    • Shift + Right Arrow: Extends selection one cell to the right.

    • Shift + Left Arrow: Extends selection one cell to the left.

    • Shift + Down Arrow: Extends selection one cell down.

    • Shift + Up Arrow: Extends selection one cell Up.

    Note

    See § 6.8 Key Assignment Conventions for Common Functionsarrow-up-right for cut, copy, and paste key assignments.

    Keyboard Interaction - Setting Focus and Navigating Inside Cells

    This section describes two important aspects of keyboard interaction design shared by both data and layout grid patterns:

    1. Choosing whether a cell or an element inside a cell receives focus in response to grid navigation key events.

    2. Enabling grid navigation keys to be used to interact with elements inside of a cell.

    Whether to Focus on a Cell Or an Element Inside It

    For assistive technology users, the quality of experience when navigating a grid heavily depends on both what a cell contains and on where keyboard focus is set. For example, if a cell contains a button and a grid navigation key places focus on the cell instead of the button, screen readers announce the button label but do not tell users a button is present.

    There are two optimal cell design and focus behavior combinations:

    1. A cell contains one widget whose operation does not require arrow keys and grid navigation keys set focus on that widget. Examples of such widgets include link, button, menubutton, toggle button, radio button (not radio group), switch, and checkbox.

    2. A cell contains text or a single graphic and grid navigation keys set focus on the cell.

    While any combination of widgets, text, and graphics may be included in a single cell, grids that do not follow one of these two cell design and focus movement patterns add complexity for authors or users or both. The reference implementations included in the example section below demonstrate some strategies for making other cell designs as accessible as possible, but the most widely accessible experiences are likely to come by applying the above two patterns.

    Editing and Navigating Inside a Cell

    While navigation keys, such as arrow keys, are moving focus from cell to cell, they are not available to perform actions like operate a combobox or move an editing caret inside of a cell. The user may need keys that are used for grid navigation to operate elements inside a cell if a cell contains:

    1. Editable content.

    2. Multiple widgets.

    3. A widget that utilizes arrow keys in its interaction model, such as a radio group or slider.

    Following are common keyboard conventions for disabling and restoring grid navigation functions.

    • Enter: Disables grid navigation and:

      • If the cell contains editable content, places focus in an input field, such as a textboxarrow-up-right. If the input is a single-line text field, a subsequent press of Enter may either restore grid navigation functions or move focus to an input field in a neighboring cell.

      • If the cell contains one or more widgets, places focus on the first widget.

    • F2:

      • If the cell contains editable content, places focus in an input field, such as a . A subsequent press of F2 restores grid navigation functions.

      • If the cell contains one or more widgets, places focus on the first widget. A subsequent press of F2 restores grid navigation functions.

    • Alphanumeric keys: If the cell contains editable content, places focus in an input field, such as a .

    When grid navigation is disabled, conventional changes to navigation behaviors include:

    • Escape: restores grid navigation. If content was being edited, it may also undo edits.

    • Right Arrow or Down Arrow: If the cell contains multiple widgets, moves focus to the next widget inside the cell, optionally wrapping to the first widget if focus is on the last widget. Otherwise, passes the key event to the focused widget.

    • Left Arrow or Up Arrow: If the cell contains multiple widgets, moves focus to the previous widget inside the cell, optionally wrapping to the first widget if focus is on the last widget. Otherwise, passes the key event to the focused widget.

    • Tab: moves focus to the next widget in the grid. Optionally, the focus movement may wrap inside a single cell or within the grid itself.

    • Shift + Tab: moves focus to the previous widget in the grid. Optionally, the focus movement may wrap inside a single cell or within the grid itself.

    WAI-ARIA Roles, States, and Properties

    • The grid container has role gridarrow-up-right.

    • Each row container has role rowarrow-up-right and is either a DOM descendant of or owned by the grid element or an element with role rowgrouparrow-up-right.

    • Each cell is either a DOM descendant of or owned by a row element and has one of the following roles:

      • if the cell contains a title or header information for the column.

      • if the cell contains title or header information for the row.

      • if the cell does not contain column or row header information.

    • If there is an element in the user interface that serves as a label for the grid, is set on the grid element with a value that refers to the labelling element. Otherwise, a label is specified for the grid element using .

    • If the grid has a caption or description, is set on the grid element with a value referring to the element containing the description.

    • If the grid provides sort functions, is set to an appropriate value on the header cell element for the sorted column or row as described in the section on .

    • If the grid supports selection, when a cell or row is selected, the selected element has set true. If the grid supports column selection and a column is selected, all cells in the column have aria-selected set to true.

    • If the grid provides content editing functionality and contains cells that may have edit capabilities disabled in certain conditions, may be set true on cells where editing is disabled. If edit functions are disabled for all cells, aria-readonly may be set true on the grid element. Grids that do not provide editing functions do not include the aria-readonly attribute on any of their elements.

    • If there are conditions where some rows or columns are hidden or not present in the DOM, e.g., data is dynamically loaded when scrolling or the grid provides functions for hiding rows or columns, the following properties are applied as described in the section on .

      • or is set to the total number of columns or rows, respectively.

      • or

    • If the grid includes cells that span multiple rows or multiple columns, and if the grid role is NOT applied to an HTML table element, then or is applied as described in .

    Note

    • If the element with the grid role is an HTML table element, then it is not necessary to use ARIA roles for rows and cells because the HTML elements have implied ARIA semantics. For example, an HTML <TR> has an implied ARIA role of row. A grid built from an HTML table that includes cells that span multiple rows or columns must use HTML rowspan and colspan and must not use aria-rowspan or aria-colspan.

    • If rows or cells are included in a grid via , they will be presented to assistive technologies after the DOM descendants of the grid element unless the DOM descendants are also included in the aria-owns attribute.

    hashtag
    3.13 Link

    A linkarrow-up-right widget provides an interactive reference to a resource. The target resource can be either external or local, i.e., either outside or within the current page or application.

    Note

    Authors are strongly encouraged to use a native host language link element, such as an HTML <A> element with an href attribute. As with other WAI-ARIA widget roles, applying the link role to an element will not cause browsers to enhance the element with standard link behaviors, such as navigation to the link target or context menu actions. When using the link role, providing these features of the element is the author's responsibility.

    Examples

    Link Examplesarrow-up-right: Link widgets constructed from HTML span and img elements.

    Keyboard Interaction

    • Enter: Executes the link and moves focus to the link target.

    • Shift + F10 (Optional): Opens a context menu for the link.

    WAI-ARIA Roles, States, and Properties

    The element containing the link text or graphic has role of linkarrow-up-right.

    hashtag
    3.14 Listbox

    A listboxarrow-up-right widget presents a list of options and allows a user to select one or more of them. A listbox that allows a single option to be chosen is a single-select listbox; one that allows multiple options to be selected is a multi-select listbox.

    When screen readers present a listbox, they may render the name, state, and position of each option in the list. The name of an option is a string calculated by the browser, typically from the content of the option element. As a flat string, the name does not contain any semantic information. Thus, if an option contains a semantic element, such as a heading, screen reader users will not have access to the semantics. In addition, the interaction model conveyed by the listbox role to assistive technologies does not support interacting with elements inside of an option. Because of these traits of the listbox widget, it does not provide an accessible way to present a list of interactive elements, such as links, buttons, or checkboxes. To present a list of interactive elements, see the gridarrow-up-right pattern.

    Avoiding very long option names facilitates understandability and perceivability for screen reader users. The entire name of an option is spoken as a single unit of speech when the option is read. When too much information is spoken as the result of a single key press, it is difficult to understand. Long names inhibit perception by increasing the impact of interrupted speech because users typically have to re-read the entire option. And, if the user does not understand what is spoken, reading the name by character, word, or phrase may be a difficult operation for many screen reader users in the context of a listbox widget.

    Sets of options where each option name starts with the same word or phrase can also significantly degrade usability for keyboard and screen reader users. Scrolling through the list to find a specific option becomes inordinately time consuming for a screen reader user who must listen to that word or phrase repeated before hearing what is unique about each option. For example, if a listbox for choosing a city were to contain options where each city name were preceded by a country name, and if many cities were listed for each country, a screen reader user would have to listen to the country name before hearing each city name. In such a scenario, it would be better to have 2 list boxes, one for country and one for city.

    Examples

    • Scrollable Listbox Examplearrow-up-right: Single-select listbox that scrolls to reveal more options, similar to HTML select with size attribute greater than one.

    • Collapsible Dropdown Listbox Examplearrow-up-right: Single-select collapsible listbox that expands when activated, similar to HTML select with the attribute size="1".

    • : Examples of both single-select and multi-select listboxes with accompanying toolbars where options can be added, moved, and removed.

    Keyboard Interaction

    For a vertically oriented listbox:

    • When a single-select listbox receives focus:

      • If none of the options are selected before the listbox receives focus, the first option receives focus. Optionally, the first option may be automatically selected.

      • If an option is selected before the listbox receives focus, focus is set on the selected option.

    • When a multi-select listbox receives focus:

      • If none of the options are selected before the listbox receives focus, focus is set on the first option and there is no automatic change in the selection state.

      • If one or more options are selected before the listbox receives focus, focus is set on the first option in the list that is selected.

    • Down Arrow: Moves focus to the next option. Optionally, in a single-select listbox, selection may also move with focus.

    • Up Arrow: Moves focus to the previous option. Optionally, in a single-select listbox, selection may also move with focus.

    • Home (Optional): Moves focus to first option. Optionally, in a single-select listbox, selection may also move with focus. Supporting this key is strongly recommended for lists with more than five options.

    • End (Optional): Moves focus to last option. Optionally, in a single-select listbox, selection may also move with focus. Supporting this key is strongly recommended for lists with more than five options.

    • Type-ahead is recommended for all listboxes, especially those with more than seven options:

      • Type a character: focus moves to the next item with a name that starts with the typed character.

      • Type multiple characters in rapid succession: focus moves to the next item with a name that starts with the string of characters typed.

    • Multiple Selection: Authors may implement either of two interaction models to support multiple selection: a recommended model that does not require the user to hold a modifier key, such as Shift or Control, while navigating the list or an alternative model that does require modifier keys to be held while navigating in order to avoid losing selection states.

      • Recommended selection model -- holding modifier keys is not necessary:

    Note

    1. DOM focus (the active element) is functionally distinct from the selected state. For more details, see this description of differences between focus and selection.arrow-up-right

    2. The listbox role supports the aria-activedescendantarrow-up-right property, which provides an alternative to moving DOM focus among option elements when implementing keyboard navigation. For details, see Managing Focus in Composites Using aria-activedescendantarrow-up-right.

    3. In a single-select listbox, moving focus may optionally unselect the previously selected option and select the newly focused option. This model of selection is known as "selection follows focus". Having selection follow focus can be very helpful in some circumstances and can severely degrade accessibility in others. For additional guidance, see .

    4. If selecting or unselecting all options is an important function, implementing separate controls for these actions, such as buttons for "Select All" and "Unselect All", significantly improves accessibility.

    5. If the options in a listbox are arranged horizontally:

      1. Down Arrow performs as Right Arrow is described above, and vice versa.

      2. Up Arrow performs as Left Arrow is described above, and vice versa.

    WAI-ARIA Roles, States, and Properties

    • An element that contains or owns all the listbox options has role listboxarrow-up-right.

    • Each option in the listbox has role optionarrow-up-right and is a DOM descendant of the element with role listbox or is referenced by an aria-ownsarrow-up-right property on the listbox element.

    • If the listbox is not part of another widget, then it has a visible label referenced by on the element with role listbox.

    • In a single-select listbox, the selected option has set to true.

    • if the listbox supports multiple selection:

      • The element with role listbox has set to true.

      • All selected options have

    • If the complete set of available options is not present in the DOM due to dynamic loading as the user scrolls, their and attributes are set appropriately.

    • If options are arranged horizontally, the element with role listbox has set to horizontal. The default value of aria-orientation for listbox is vertical.

    hashtag
    3.17 Radio Group

    A radio group is a set of checkable buttons, known as radio buttons, where no more than one of the buttons can be checked at a time. Some implementations may initialize the set with all buttons in the unchecked state in order to force the user to check one of the buttons before moving past a certain point in the workflow.

    Examples

    • Radio Group Example Using Roving tabindexarrow-up-right

    • Radio Group Example Using aria-activedescendantarrow-up-right

    Keyboard Interaction

    For Radio Groups Not Contained in a Toolbar

    This section describes the keyboard interaction implemented for most radio groups. For the special case of a radio group nested inside a toolbararrow-up-right, use the keyboard interaction described in the following section.

    • Tab and Shift + Tab: Move focus into and out of the radio group. When focus moves into a radio group :

      • If a radio button is checked, focus is set on the checked button.

      • If none of the radio buttons are checked, focus is set on the first radio button in the group.

    • Space: checks the focused radio button if it is not already checked.

    • Right Arrow and Down Arrow: move focus to the next radio button in the group, uncheck the previously focused button, and check the newly focused button. If focus is on the last button, focus moves to the first button.

    • Left Arrow and Up Arrow: move focus to the previous radio button in the group, uncheck the previously focused button, and check the newly focused button. If focus is on the first button, focus moves to the last button.

    Note

    The initial focus behavior described above differs slightly from the behavior provided by some browsers for native HTML radio groups. In some browsers, if none of the radio buttons are selected, moving focus into the radio group with Shift+Tab will place focus on the last radio button instead of the first radio button.

    For Radio Group Contained in a Toolbar

    Because arrow keys are used to navigate among elements of a toolbar and the Tab key moves focus in and out of a toolbar, when a radio group is nested inside a toolbar, the keyboard interaction of the radio group is slightly different from that of a radio group that is not inside of a toolbar. For instance, users need to be able to navigate among all toolbar elements, including the radio buttons, without changing which radio button is checked. So, when navigating through a radio group in a toolbar with arrow keys, the button that is checked does not change. The keyboard interaction of a radio group nested in a toolbar is as follows.

    • Space: If the focused radio button is not checked, unchecks the currently checked radio button and checks the focused radio button. Otherwise, does nothing.

    • Enter (optional): If the focused radio button is not checked, unchecks the currently checked radio button and checks the focused radio button. Otherwise, does nothing.

    • Right Arrow:

      • When focus is on a radio button and that radio button is not the last radio button in the radio group, moves focus to the next radio button.

      • When focus is on the last radio button in the radio group and that radio button is not the last element in the toolbar, moves focus to the next element in the toolbar.

      • When focus is on the last radio button in the radio group and that radio button is also the last element in the toolbar, moves focus to the first element in the toolbar.

    • Left Arrow:

      • When focus is on a radio button and that radio button is not the first radio button in the radio group, moves focus to the previous radio button.

      • When focus is on the first radio button in the radio group and that radio button is not the first element in the toolbar, moves focus to the previous element in the toolbar.

    • Down Arrow (optional): Moves focus to the next radio button in the radio group. If focus is on the last radio button in the radio group, moves focus to the first radio button in the group.

    • Up Arrow (optional): Moves focus to the previous radio button in the radio group. If focus is on the first radio button in the radio group, moves focus to the last radio button in the group.

    Note

    Radio buttons in a toolbar are frequently styled in a manner that appears more like toggle buttons. For an example, See the Simple Editor Toolbar Examplearrow-up-right

    WAI-ARIA Roles, States, and Properties

    • The radio buttons are contained in or owned by an element with role radiogrouparrow-up-right.

    • Each radio button element has role radioarrow-up-right.

    • If a radio button is checked, the radio element has aria-checkedarrow-up-right set to true. If it is not checked, it has set to false.

    • Each radio element is labelled by its content, has a visible label referenced by , or has a label specified with .

    • The radiogroup element has a visible label referenced by or has a label specified with .

    • If elements providing additional information about either the radio group or each radio button are present, those elements are referenced by the radiogroup element or radio elements with the property.

    hashtag
    3.18 Slider

    A slider is an input where the user selects a value from within a given range. Sliders typically have a slider thumb that can be moved along a bar or track to change the value of the slider.

    Examples

    • Horizontal Slider Examplesarrow-up-right: Demonstrates using three horizontally aligned sliders to make a color picker.

    • Slider Examples with aria-orientation and aria-valuetextarrow-up-right: Three thermostat control sliders that demonstrate using aria-orientation and aria-valuetext.

    Keyboard Interaction

    • Right Arrow: Increase the value of the slider by one step.

    • Up Arrow: Increase the value of the slider by one step.

    • Left Arrow: Decrease the value of the slider by one step.

    • Down Arrow: Decrease the value of the slider by one step.

    • Home: Set the slider to the first allowed value in its range.

    • End: Set the slider to the last allowed value in its range.

    • Page Up (Optional): Increment the slider by an amount larger than the step change made by Up Arrow.

    • Page Down (Optional): Decrement the slider by an amount larger than the step change made by Down Arrow.

    Note

    1. Focus is placed on the slider (the visual object that the mouse user would move, also known as the thumb.

    2. In some circumstances, reversing the direction of the value change for the keys specified above, e.g., having Up Arrow decrease the value, could create a more intuitive experience.

    WAI-ARIA Roles, States, and Properties

    • The element serving as the focusable slider control has role sliderarrow-up-right.

    • The slider element has the aria-valuenowarrow-up-right property set to a decimal value representing the current value of the slider.

    • The slider element has the aria-valueminarrow-up-right property set to a decimal value representing the minimum allowed value of the slider.

    • The slider element has the property set to a decimal value representing the maximum allowed value of the slider.

    • If the value of aria-valuenow is not user-friendly, e.g., the day of the week is represented by a number, the property is set to a string that makes the slider value understandable, e.g., "Monday".

    • If the slider has a visible label, it is referenced by on the slider element. Otherwise, the slider element has a label provided by .

    • If the slider is vertically oriented, it has set to vertical. The default value of aria-orientation for a slider is horizontal.

    hashtag
    3.19 Slider (Multi-Thumb)

    A multi-thumb slider is a sliderarrow-up-right with two or more thumbs that each set a value in a group of related values. For example, in a product search, a two-thumb slider could be used to enable users to set the minimum and maximum price limits for the search. In many two-thumb sliders, the thumbs are not allowed to pass one another, such as when the slider sets the minimum and maximum values for a range. For example, in a price range selector, the maximum value of the thumb that sets the lower end of the range is limited by the current value of the thumb that sets the upper end of the range. Conversely, the minimum value of the upper end thumb is limited by the current value of the lower end thumb. However, in some multi-thumb sliders, each thumb sets a value that does not depend on the other thumb values.

    Example

    Multi-Thumb Slider Examplesarrow-up-right: Demonstrates two-thumb sliders for picking price ranges for an airline flight and hotel reservation.

    Keyboard Interaction

    • Each thumb is in the page tab sequence and has the same keyboard interaction as a single-thumb sliderarrow-up-right.

    • The tab order remains constant regardless of thumb value and visual position within the slider. For example, if the value of a thumb changes such that it moves past one of the other thumbs, the tab order does not change.

    WAI-ARIA Roles, States, and Properties

    • Each element serving as a focusable slider thumb has role sliderarrow-up-right.

    • Each slider element has the aria-valuenowarrow-up-right property set to a decimal value representing the current value of the slider.

    • Each slider element has the aria-valueminarrow-up-right property set to a decimal value representing the minimum allowed value of the slider.

    • Each slider element has the property set to a decimal value representing the maximum allowed value of the slider.

    • When the range (e.g. minimum and/or maximum value) of another slider is dependent on the current value of a slider, the values of or of the dependent sliders are updated when the value changes.

    • If a value of aria-valuenow is not user-friendly, e.g., the day of the week is represented by a number, the property is set to a string that makes the slider value understandable, e.g., "Monday".

    • If a slider has a visible label, it is referenced by on the slider element. Otherwise, the slider element has a label provided by .

    • If a slider is vertically oriented, it has set to vertical. The default value of aria-orientation for a slider is horizontal.

    hashtag
    3.20 Spinbutton

    A spinbutton is an input widget that restricts its value to a set or range of discrete values. For example, in a widget that enables users to set an alarm, a spinbutton could allow users to select a number from 0 to 59 for the minute of an hour.

    Spinbuttons often have three components, including a text field that displays the current value, an increment button, and a decrement button. The text field is usually the only focusable component because the increment and decrement functions are keyboard accessible via arrow keys. Typically, the text field also allows users to directly edit the value.

    If the range is large, a spinbutton may support changing the value in both small and large steps. For instance, in the alarm example, the user may be able to move by 1 minute with Up Arrow and Down Arrow and by 10 minutes with Page Up and Page Down.

    Example

    Date Picker Spin Button Example:arrow-up-right Illustrates a date picker made from thre spin buttons for day, month, and year.

    Keyboard Interaction

    • Up Arrow: Increases the value.

    • Down Arrow: Decreases the value.

    • Home: If the spinbutton has a minimum value, sets the value to its minimum.

    • End: If the spinbutton has a maximum value, sets the value to its maximum.

    • Page Up (Optional): Increases the value by a larger step than Up Arrow.

    • Page Down (Optional): Decreases the value by a larger step than Down Arrow.

    • If the spinbutton text field allows directly editing the value, the following keys are supported:

      • Standard single line text editing keys appropriate for the device platform (see note below).

      • Printable Characters: Type characters in the textbox. Note that many implementations allow only certain characters as part of the value and prevent input of any other characters. For example, an hour-and-minute spinner would allow only integer values from 0 to 59, the colon ':', and the letters 'AM' and 'PM'. Any other character input does not change the contents of the text field nor the value of the spinbutton.

    Note

    1. Focus remains on the text field during operation.

    2. Standard single line text editing keys appropriate for the device platform:

      1. include keys for input, cursor movement, selection, and text manipulation.

      2. Standard key assignments for editing functions depend on the device operating system.

      3. The most robust approach for providing text editing functions is to rely on browsers, which supply them for HTML inputs with type text and for elements with the contenteditable HTML attribute.

      4. IMPORTANT: Be sure that JavaScript does not interfere with browser-provided text editing functions by capturing key events for the keys used to perform them.

    WAI-ARIA Roles, States, and Properties

    • The focusable element serving as the spinbutton has role spinbuttonarrow-up-right. This is typically an element that supports text input.

    • The spinbutton element has the aria-valuenowarrow-up-right property set to a decimal value representing the current value of the spinbutton.

    • The spinbutton element has the aria-valueminarrow-up-right property set to a decimal value representing the minimum allowed value of the spinbutton if it has a known minimum value.

    • The spinbutton element has the property set to a decimal value representing the maximum allowed value of the spinbutton if it has a known maximum value.

    • If the value of aria-valuenow is not user-friendly, e.g., the day of the week is represented by a number, the property is set on the spinbutton element to a string that makes the spinbutton value understandable, e.g., "Monday".

    • If the spinbutton has a visible label, it is referenced by on the spinbutton element. Otherwise, the spinbutton element has a label provided by .

    • The spinbutton element has set to true if the value is outside the allowed range. Note that most implementations prevent input of invalid values, but in some scenarios, blocking all invalid input may not be practical.

    hashtag
    3.21 Table

    Like an HTML table element, a WAI-ARIA tablearrow-up-right is a static tabular structure containing one or more rows that each contain one or more cells; it is not an interactive widget. Thus, its cells are not focusable or selectable. The grid patternarrow-up-right is used to make an interactive widget that has a tabular structure.

    However, tables are often used to present a combination of information and interactive widgets. Since a table is not a widget, each widget contained in a table is a separate stop in the page tab sequence. If the number of widgets is large, replacing the table with a grid can dramatically reduce the length of the page tab sequence because a grid is a composite widget that can contain other widgets.

    Note

    As with other WAI-ARIA roles that have a native host language equivalent, authors are strongly encouraged to use a native HTML table element whenever possible. This is especially important with role table because it is a new feature of WAI-ARIA 1.1. It is thus advisable to test implementations thoroughly with each browser and assistive technology combination that could be used by the target audience.

    Examples

    Table Examplearrow-up-right: ARIA table made using HTML div and span elements.

    Keyboard Interaction

    Not applicable.

    WAI-ARIA Roles, States, and Properties

    • The table container has role tablearrow-up-right.

    • Each row container has role rowarrow-up-right and is either a DOM descendant of or owned by the table element or an element with role rowgrouparrow-up-right.

    • Each cell is either a DOM descendant of or owned by a row element and has one of the following roles:

      • if the cell contains a title or header information for the column.

      • if the cell contains title or header information for the row.

      • if the cell does not contain column or row header information.

    • If there is an element in the user interface that serves as a label for the table, is set on the table element with a value that refers to the labelling element. Otherwise, a label is specified for the table element using .

    • If the table has a caption or description, is set on the table element with a value referring to the element containing the description.

    • If the table contains sortable columns or rows, is set to an appropriate value on the header cell element for the sorted column or row as described in the section on .

    • If there are conditions where some rows or columns are hidden or not present in the DOM, e.g., there are widgets on the page for hiding rows or columns, the following properties are applied as described in the section on .

      • or is set to the total number of columns or rows, respectively.

      • or is set to the position of a cell within a row or column, respectively.

    • If the table includes cells that span multiple rows or multiple columns, then or is applied as described in .

    Note

    If rows or cells are included in a table via aria-ownsarrow-up-right, they will be presented to assistive technologies after the DOM descendants of the table element unless the DOM descendants are also included in the aria-owns attribute.

    hashtag
    3.22 Tabs

    Tabs are a set of layered sections of content, known as tab panels, that display one panel of content at a time. Each tab panel has an associated tab element, that when activated, displays the panel. The list of tab elements is arranged along one edge of the currently displayed panel, most commonly the top edge.

    Terms used to describe this design pattern include:

    Tabs or Tabbed Interface

    A set of tab elements and their associated tab panels.

    Tab List

    A set of tab elements contained in a tablistarrow-up-right element.

    tabarrow-up-right

    An element in the tab list that serves as a label for one of the tab panels and can be activated to display that panel.

    tabpanelarrow-up-right

    The element that contains the content associated with a tab.

    When a tabbed interface is initialized, one tab panel is displayed and its associated tab is styled to indicate that it is active. When the user activates one of the other tab elements, the previously displayed tab panel is hidden, the tab panel associated with the activated tab becomes visible, and the tab is considered "active".

    Examples

    • Tabs With Automatic Activationarrow-up-right: A tabs widget where tabs are automatically activated and their panel is displayed when they receive focus.

    • Tabs With Manual Activationarrow-up-right: A tabs widget where users activate a tab and display its panel by pressing Space or Enter.

    Keyboard Interaction

    For the tab list:

    • Tab: When focus moves into the tab list, places focus on the active tab element. When the tab list contains the focus, moves focus to the next element in the page tab sequence outside the tablist, which is typically either the first focusable element inside the tab panel or the tab panel itself.

    • When focus is on a tab element in a horizontal tab list:

      • Left Arrow: moves focus to the previous tab. If focus is on the first tab, moves focus to the last tab. Optionally, activates the newly focused tab (See note below).

      • Right Arrow: Moves focus to the next tab. If focus is on the last tab element, moves focus to the first tab. Optionally, activates the newly focused tab (See note below).

    • When focus is on a tab in a tablist with either horizontal or vertical orientation:

      • Space or Enter: Activates the tab if it was not activated automatically on focus.

      • Home (Optional): Moves focus to the first tab. Optionally, activates the newly focused tab (See note below).

    Note

    1. It is recommended that tabs activate automatically when they receive focus as long as their associated tab panels are displayed without noticeable latency. This typically requires tab panel content to be preloaded. Otherwise, automatic activation slows focus movement, which significantly hampers users' ability to navigate efficiently across the tab list. For additional guidance, see § 6.4 Deciding When to Make Selection Automatically Follow Focusarrow-up-right.

    2. If the tabs in a tab list are arranged vertically:

      1. Down Arrow performs as Right Arrow is described above.

      2. Up Arrow performs as Left Arrow is described above.

    3. If the tab list is horizontal, it does not listen for Down Arrow or Up Arrow so those keys can provide their normal browser scrolling functions even when focus is inside the tab list.

    WAI-ARIA Roles, States, and Properties

    • The element that serves as the container for the set of tabs has role tablistarrow-up-right.

    • Each element that serves as a tab has role tabarrow-up-right and is contained within the element with role tablist.

    • Each element that contains the content panel for a tab has role .

    • If the tab list has a visible label, the element with role tablist has set to a value that refers to the labelling element. Otherwise, the tablist element has a label provided by .

    • Each element with role tab has the property referring to its associated tabpanel element.

    • The active tab element has the state set to true and all other tab elements have it set to false.

    • Each element with role tabpanel has the property referring to its associated tab element.

    • If a tab element has a pop-up menu, it has the property set to either menu or true.

    • If the tablist element is vertically oriented, it has the property set to vertical. The default value of aria-orientation for a tablist element is horizontal.

    hashtag
    3.23 Toolbar

    A toolbararrow-up-right is a container for grouping a set of controls, such as buttons, menubuttons, or checkboxes.

    When a set of controls is visually presented as a group, the toolbar role can be used to communicate the presence and purpose of the grouping to screen reader users. Grouping controls into toolbars can also be an effective way of reducing the number of tab stops in the keyboard interface.

    To optimize the benefit of toolbar widgets:

    • Implement focus management so the keyboard tab sequence includes one stop for the toolbar and arrow keys move focus among the controls in the toolbar.

      • In horizontal toolbars, Left Arrow and Right Arrow navigate among controls. Up Arrow and Down Arrow can duplicate Left Arrow and Right Arrow, respectively, or can be reserved for operating controls, such as spin buttons that require vertical arrow keys to operate.

      • In vertical toolbars, Up Arrow and Down Arrow navigate among controls. Left Arrow and Right Arrow can duplicate Up Arrow and Down Arrow, respectively, or can be reserved for operating controls, such as horizontal sliders that require horizontal arrow keys to operate.

      • In toolbars with multiple rows of controls, Left Arrow and Right Arrow can provide navigation that wraps from row to row, leaving the option of reserving vertical arrow keys for operating controls.

    • Avoid including controls whose operation requires the pair of arrow keys used for toolbar navigation. If unavoidable, include only one such control and make it the last element in the toolbar. For example, in a horizontal toolbar, a textbox could be included as the last element.

    • Use toolbar as a grouping element only if the group contains 3 or more controls.

    Example

    Toolbar Examplearrow-up-right: A toolbar that uses roving tabindex to manage focus and contains several types of controls, including toggle buttons, radio buttons, a menu button, a spin button, a checkbox, and a link.

    Keyboard Interaction

    • Tab and Shift + Tab: Move focus into and out of the toolbar. When focus moves into a toolbar:

      • If focus is moving into the toolbar for the first time, focus is set on the first control that is not disabled.

      • If the toolbar has previously contained focus, focus is optionally set on the control that last had focus. Otherwise, it is set on the first control that is not disabled.

    • For a horizontal toolbar (the default):

      • Left Arrow: Moves focus to the previous control. Optionally, focus movement may wrap from the first element to the last element.

      • Right Arrow: Moves focus to the next control. Optionally, focus movement may wrap from the last element to the first element.

    • Home (Optional): Moves focus to first element.

    • End (Optional): Moves focus to last element.

    Note

    1. If the items in a toolbar are arranged vertically:

      1. Down Arrow performs as Right Arrow is described above.

      2. Up Arrow performs as Left Arrow is described above.

    2. Typically, disabled elements are not focusable when navigating with a keyboard. However, in circumstances where discoverability of a function is crucial, it may be helpful if disabled controls are focusable so screen reader users are more likely to be aware of their presence. For additional guidance, see .

    3. In applications where quick access to a toolbar is important, such as accessing an editor's toolbar from its text area, a documented shortcut key for moving focus from the relevant context to its corresponding toolbar is recommended.

    WAI-ARIA Roles, States, and Properties

    • The element that serves as the toolbar container has role toolbararrow-up-right.

    • If the toolbar has a visible label, it is referenced by aria-labelledbyarrow-up-right on the toolbar element. Otherwise, the toolbar element has a label provided by aria-labelarrow-up-right.

    • If the controls are arranged vertically, the toolbar element has aria-orientationarrow-up-right set to vertical. The default orientation is horizontal.

    hashtag
    3.24 Tooltip Widget

    NOTE: This design pattern is work in progress; it does not yet have task force consensus. Progress and discussions are captured in issue 128.arrow-up-right

    A tooltip is a popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it. It typically appears after a small delay and disappears when Escape is pressed or on mouse out.

    Tooltip widgets do not receive focus. A hover that contains focusable elements can be made using a non-modal dialog.

    Example

    Work to develop a tooltip example is tracked by issue 127.arrow-up-right

    Keyboard Interaction

    Escape: Dismisses the Tooltip.

    Note

    1. Focus stays on the triggering element while the tooltip is displayed.

    2. If the tooltip is invoked when the trigger element receives focus, then it is dismissed when it no longer has focus (onBlur). If the tooltip is invoked with mouseIn, then it is dismissed with on mouseOut.

    WAI-ARIA Roles, States, and Properties

    • The element that serves as the tooltip container has role tooltiparrow-up-right.

    • The element that triggers the tooltip references the tooltip element with aria-describedbyarrow-up-right.

    hashtag
    3.25 Tree View

    A tree view widget presents a hierarchical list. Any item in the hierarchy may have child items, and items that have children may be expanded or collapsed to show or hide the children. For example, in a file system navigator that uses a tree view to display folders and files, an item representing a folder can be expanded to reveal the contents of the folder, which may be files, folders, or both.

    Terms for understanding tree views include:

    Node

    An item in a tree.

    Root Node

    Node at the base of the tree; it may have one or more child nodes but does not have a parent node.

    Child Node

    Node that has a parent; any node that is not a root node is a child node.

    End Node

    Node that does not have any child nodes; an end node may be either a root node or a child node.

    Parent Node

    Node with one or more child nodes. It can be open (expanded) or closed (collapsed).

    Open Node

    Parent node that is expanded so its child nodes are visible.

    Closed Node

    Parent node that is collapsed so the child nodes are not visible.

    When using a keyboard to navigate a tree, a visual keyboard indicator informs the user which node is focused. If the tree allows the user to choose just one item for an action, then it is known as a single-select tree. In some implementations of single-select tree, the focused item also has a selected state; this is known as selection follows focus. However, in multi-select trees, which enable the user to select more than one item for an action, the selected state is always independent of the focus. For example, in a typical file system navigator, the user can move focus to select any number of files for an action, such as copy or move. It is important that the visual design distinguish between items that are selected and the item that has focus. For more details, see this description of differences between focus and selectionarrow-up-right and Deciding When to Make Selection Automatically Follow Focusarrow-up-right.

    Examples

    • File Directory Treeview Example Using Computed Propertiesarrow-up-right: A file selector tree that demonstrates browser support for automatically computing aria-level, aria-posinset and aria-setsize based on DOM structure.

    • File Directory Treeview Example Using Declared Propertiesarrow-up-right: A file selector tree that demonstrates how to explicitly define values for aria-level, aria-posinset and aria-setsize.

    • : A tree that provides navigation to a set of web pages and demonstrates browser support for automatically computing aria-level, aria-posinset and aria-setsize based on DOM structure.

    • : A tree that provides navigation to a set of web pages and demonstrates how to explicitly define values for aria-level, aria-posinset and aria-setsize.

    Keyboard Interaction

    For a vertically oriented tree:

    • When a single-select tree receives focus:

      • If none of the nodes are selected before the tree receives focus, focus is set on the first node.

      • If a node is selected before the tree receives focus, focus is set on the selected node.

    • When a multi-select tree receives focus:

      • If none of the nodes are selected before the tree receives focus, focus is set on the first node.

      • If one or more nodes are selected before the tree receives focus, focus is set on the first selected node.

    • Right arrow:

      • When focus is on a closed node, opens the node; focus does not move.

      • When focus is on a open node, moves focus to the first child node.

    • Left arrow:

      • When focus is on an open node, closes the node.

      • When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.

    • Down Arrow: Moves focus to the next node that is focusable without opening or closing a node.

    • Up Arrow: Moves focus to the previous node that is focusable without opening or closing a node.

    • Home: Moves focus to the first node in the tree without opening or closing a node.

    • End: Moves focus to the last node in the tree that is focusable without opening a node.

    • Enter: activates a node, i.e., performs its default action. For parent nodes, one possible default action is to open or close the node. In single-select trees where selection does not follow focus (see note below), the default action is typically to select the focused node.

    • Type-ahead is recommended for all trees, especially for trees with more than 7 root nodes:

      • Type a character: focus moves to the next node with a name that starts with the typed character.

      • Type multiple characters in rapid succession: focus moves to the next node with a name that starts with the string of characters typed.

    • * (Optional): Expands all siblings that are at the same level as the current node.

    • Selection in multi-select trees: Authors may implement either of two interaction models to support multiple selection: a recommended model that does not require the user to hold a modifier key, such as Shift or Control, while navigating the list or an alternative model that does require modifier keys to be held while navigating in order to avoid losing selection states.

      • Recommended selection model -- holding a modifier key while moving focus is not necessary:

    Note

    1. DOM focus (the active element) is functionally distinct from the selected state. For more details, see this description of differences between focus and selection.arrow-up-right

    2. The tree role supports the aria-activedescendantarrow-up-right property, which provides an alternative to moving DOM focus among treeitem elements when implementing keyboard navigation. For details, see Managing Focus in Composites Using aria-activedescendantarrow-up-right.

    3. In a single-select tree, moving focus may optionally unselect the previously selected node and select the newly focused node. This model of selection is known as "selection follows focus". Having selection follow focus can be very helpful in some circumstances and can severely degrade accessibility in others. For additional guidance, see .

    4. If selecting or unselecting all nodes is an important function, implementing separate controls for these actions, such as buttons for "Select All" and "Unselect All", significantly improves accessibility.

    5. If the nodes in a tree are arranged horizontally:

      1. Down Arrow performs as Right Arrow is described above, and vice versa.

      2. Up Arrow performs as Left Arrow is described above, and vice versa.

    WAI-ARIA Roles, States, and Properties

    • All tree nodes are contained in or owned by an element with role treearrow-up-right.

    • Each element serving as a tree node has role treeitemarrow-up-right.

    • Each root node is contained in the element with role tree or referenced by an aria-ownsarrow-up-right property set on the tree element.

    • Each parent node contains or owns an element with role .

    • Each child node is contained in or owned by an element with role that is contained in or owned by the node that serves as the parent of that child.

    • Each element with role treeitem that serves as a parent node has set to false when the node is in a closed state and set to true when the node is in an open state. End nodes do not have the aria-expanded attribute because, if they were to have it, they would be incorrectly described to assistive technologies as parent nodes.

    • If the tree supports selection of more than one node, the element with role tree has set to true. Otherwise, aria-multiselectable is either set to false or the default value of false is implied.

    • If the tree does not support multiple selection, is set to true for the selected node and it is not present on any other node in the tree.

    • if the tree supports multiple selection:

      • All selected nodes have set to true.

      • All nodes that are selectable but not selected have set to false

    • The element with role tree has either a visible label referenced by or a value specified for .

    • If the complete set of available nodes is not present in the DOM due to dynamic loading as the user moves focus in or scrolls the tree, each node has , , and specified.

    • If the tree element is horizontally oriented, it has set to horizontal. The default value of aria-orientation for a tree is vertical.

    Note

    If aria-ownsarrow-up-right is set on the tree container to include elements that are not DOM children of the container, those elements will appear in the reading order in the sequence they are referenced and after any items that are DOM children. Scripts that manage focus need to ensure the visual focus order matches this assistive technology reading order.

    hashtag
    3.26 Treegrid

    A treegridarrow-up-right widget presents a hierarchical data grid consisting of tabular information that is editable or interactive. Any row in the hierarchy may have child rows, and rows with children may be expanded or collapsed to show or hide the children. For example, in a treegrid used to display messages and message responses for a e-mail discussion list, messages with responses would be in rows that can be expanded to reveal the response messages.

    In a treegrid both rows and cells are focusable. Every row and cell contains a focusable element or is itself focusable, regardless of whether individual cell content is editable or interactive. There is one exception: if column header cells do not provide functions, such as sort or filter, they do not need to be focusable. One reason it is important for all cells to be able to receive or contain keyboard focus is that screen readers will typically be in their application reading mode, rather than their document reading mode, when users are interacting with the grid. While in application mode, a screen reader user hears only focusable elements and content that labels focusable elements. So, screen reader users may unknowingly overlook elements contained in a treegrid that are either not focusable or not used to label a column or row.

    When using a keyboard to navigate a treegrid, a visual keyboard indicator informs the user which row or cell is focused. If the treegrid allows the user to choose just one item for an action, then it is known as a single-select treegrid, and the item with focus also has a selected state. However, in multi-select treegrids, which enable the user to select more than one row or cell for an action, the selected state is independent of the focus. For example, in a hierarchical e-mail discussion grid, the user can move focus to select any number of rows for an action, such as delete or move. It is important that the visual design distinguish between items that are selected and the item that has focus. For more details, see this description of differences between focus and selection.arrow-up-right

    Examples

    • E-mail Inbox treegrid Examplearrow-up-right: A treegrid for navigating an e-mail inbox that demonstrates three keyboard navigation models -- rows first, cells first, and cells only.

    Keyboard Interaction

    The following keys provide treegrid navigation by moving focus among rows and cells of the grid. Implementations of treegrid make these key commands available when an element in the grid has received focus, e.g., after a user has moved focus to the grid with Tab. Moving focus into the grid may result in the first cell or the first row being focused. Whether focus goes to a cell or the row depends on author preferences and whether row focus is supported, since some treegrids may not provide focus to rows.

    • Enter: If cell-only focus is enabled and focus is on the first cell with the aria-expanded property, opens or closes the child rows.,Otherwise, performs the default action for the cell.

    • Tab: If the row containing focus contains focusable elements (e.g., inputs, buttons, links, etc.), moves focus to the next input in the row. If focus is on the last focusable element in the row, moves focus out of the treegrid widget to the next focusable element.

    • Right Arrow:

      • If focus is on a collapsed row, expands the row.

      • If focus is on an expanded row or is on a row that does not have child rows, moves focus to the first cell in the row.

      • If focus is on the right-most cell in a row, focus does not move.

    • Left Arrow:

      • If focus is on an expanded row, collapses the row.

      • If focus is on a collapsed row or on a row that does not have child rows, focus does not move.

    • Down Arrow:

      • If focus is on a row, moves focus one row down. If focus is on the last row, focus does not move.

      • If focus is on a cell, moves focus one cell down. If focus is on the bottom cell in the column, focus does not move.

    • Up Arrow:

      • If focus is on a row, moves focus one row up. If focus is on the first row, focus does not move.

      • If focus is on a cell, moves focus one cell up. If focus is on the top cell in the column, focus does not move.

    • Page Down:

      • If focus is on a row, moves focus down an author-determined number of rows, typically scrolling so the bottom row in the currently visible set of rows becomes one of the first visible rows. If focus is in the last row, focus does not move.

      • If focus is on a cell, moves focus down an author-determined number of cells, typically scrolling so the bottom row in the currently visible set of rows becomes one of the first visible rows. If focus is in the last row, focus does not move.

    • Page Up:

      • If focus is on a row, moves focus up an author-determined number of rows, typically scrolling so the top row in the currently visible set of rows becomes one of the last visible rows. If focus is in the first row, focus does not move.

      • If focus is on a cell, moves focus up an author-determined number of cells, typically scrolling so the top row in the currently visible set of rows becomes one of the last visible rows. If focus is in the first row, focus does not move.

    • Home:

      • If focus is on a row, moves focus to the first row. If focus is in the first row, focus does not move.

      • If focus is on a cell, moves focus to the first cell in the row. If focus is in the first cell of the row, focus does not move.

    • End:

      • If focus is on a row, moves focus to the last row. If focus is in the last row, focus does not move.

      • If focus is on a cell, moves focus to the last cell in the row. If focus is in the last cell of the row, focus does not move.

    • Control + Home:

      • If focus is on a row, moves focus to the first row. If focus is in the first row, focus does not move.

      • If focus is on a cell, moves focus to the first cell in the column. If focus is in the first row, focus does not move.

    • Control + End:

      • If focus is on a row, moves focus to the last row. If focus is in the last row, focus does not move.

      • If focus is on a cell, moves focus to the last cell in the column. If focus is in the last row, focus does not move.

    Note

    • When the above treegrid navigation keys move focus, whether the focus is set on an element inside the cell or on the cell depends on cell content. See Whether to Focus on a Cell or an Element Inside Itarrow-up-right.

    • While navigation keys, such as arrow keys, are moving focus from cell to cell, they are not available to do something like operate a combobox or move an editing caret inside of a cell. If this functionality is needed, see Editing and Navigating Inside a Cellarrow-up-right.

    • If navigation functions can dynamically add more rows or columns to the DOM, key events that move focus to the beginning or end of the grid, such as control + End, may move focus to the last row in the DOM rather than the last available row in the back-end data.

    If a treegrid supports selection of cells, rows, or columns, the following keys are commonly used for these functions.

    • Control + Space:

      • If focus is on a row, selects all cells.

      • If focus is on a cell, selects the column that contains the focus.

    • Shift + Space:

      • If focus is on a row, selects the row.

      • If focus is on a cell, selects the row that contains the focus. If the treegrid includes a column with checkboxes for selecting rows, this key can serve as a shortcut for checking the box when focus is not on the checkbox.

    • Control + A: Selects all cells.

    • Shift + Right Arrow:

      • If focus is on a row, does not change selection.

      • if focus is on a cell, extends selection one cell to the right.

    • Shift + Left Arrow:

      • If focus is on a row, does not change selection.

      • if focus is on a cell, extends selection one cell to the left.

    • Shift + Down Arrow:

      • If focus is on a row, extends selection to all the cells in the next row.

      • If focus is on a cell, extends selection one cell down.

    • Shift + Up Arrow:

      • If focus is on a row, extends selection to all the cells in the previous row.

      • If focus is on a cell, extends selection one cell up.

    Note

    See § 6.8 Key Assignment Conventions for Common Functionsarrow-up-right for cut, copy, and paste key assignments.

    WAI-ARIA Roles, States, and Properties

    • The treegrid container has role treegridarrow-up-right.

    • Each row container has role rowarrow-up-right and is either a DOM descendant of or owned by the treegrid element or an element with role rowgrouparrow-up-right.

    • Each cell is either a DOM descendant of or owned by a row element and has one of the following roles:

      • if the cell contains a title or header information for the column.

      • if the cell contains title or header information for the row.

      • if the cell does not contain column or row header information.

    • A row that can be expanded or collapsed to show or hide a set of child rows is a parent row. Each parent row has the state set on either the row element or on a cell contained in therow. The aria-expanded state is set to false when the child rows are not displayed and set to true when the child rows are displayed. Rows that do not control display of child rows do not have the aria-expanded attribute because, if they were to have it, they would be incorrectly described to assistive technologies as parent rows.

    • If the treegrid supports selection of more than one row or cell, it is a multi-select treegrid and the element with role treegrid has set to true. Otherwise, it is a single-select treegrid, and aria-multiselectable is either set to false or the default value of false is implied.

    • If the treegrid is a single-select treegrid, is set to true on the selected row or cell, and it is not present on any other row or cell in the treegrid.

    • if the treegrid is a multi-select treegrid:

      • All selected rows or cells have set to true.

      • All rows and cells that are not selected have set to false

    • If there is an element in the user interface that serves as a label for the treegrid, is set on the grid element with a value that refers to the labelling element. Otherwise, a label is specified for the grid element using .

    • If the treegrid has a caption or description, is set on the grid element with a value referring to the element containing the description.

    • If the treegrid provides sort functions, is set to an appropriate value on the header cell element for the sorted column or row as described in the section on .

    • If the treegrid provides content editing functionality and contains cells that may have edit capabilities disabled in certain conditions, is set to true on cells where editing is disabled. If edit functions are disabled for all cells, instead of setting aria-readonly to true on every cell, aria-readonly may be set to true on the treegrid element. Treegrids that do not provide cell content editing functions do not include the aria-readonly attribute on any of their elements.

    • If there are conditions where some rows or columns are hidden or not present in the DOM, e.g., data is dynamically loaded when scrolling or the grid provides functions for hiding rows or columns, the following properties are applied as described in the section on .

      • or is set to the total number of columns or rows, respectively.

      • or

    • If the treegrid includes cells that span multiple rows or multiple columns, and if the treegrid role is NOT applied to an HTML table element, then or is applied as described in .

    Note

    • A treegrid built from an HTML table that includes cells that span multiple rows or columns must use HTML rowspan and colspan and must not use aria-rowspan or aria-colspan.

    • If rows or cells are included in a treegrid via , they will be presented to assistive technologies after the DOM descendants of the treegrid element unless the DOM descendants are also included in the aria-owns attribute.

    hashtag
    3.27 Window Splitter

    NOTE: ARIA 1.1 introduced changes to the separator role so it behaves as a widget when focusable. While this pattern has been revised to match the ARIA 1.1 specification, the task force will not complete its review until a functional example that matches the ARIA 1.1 specification is complete. Progress on this pattern is tracked by issue 129.arrow-up-right

    A window splitter is a moveable separator between two sections, or panes, of a window that enables users to change the relative size of the panes. A Window Splitter can be either variable or fixed. A fixed splitter toggles between two positions whereas a variable splitter can be adjusted to any position within an allowed range.

    A window splitter has a value that represents the size of one of the panes, which, in this pattern, is called the primary pane. When the splitter has its minimum value, the primary pane has its smallest size and the secondary pane has its largest size. The splitter also has an accessible name that matches the name of the primary pane.

    For example, consider a book reading application with a primary pane for the table of contents and a secondary pane that displays content from a section of the book. The two panes are divided by a vertical splitter labelled "Table of Contents". When the table of contents pane has its maximum size, the splitter has a value of 100, and when the table of contents is completely collapsed, the splitter has a value of 0.

    Note that the term "primary pane" does not describe the importance or purpose of content inside the pane.

    Example

    Work to develop an example window splitter widget is tracked by issue 130.arrow-up-right

    Keyboard Interaction

    • Left Arrow: Moves a vertical splitter to the left.

    • Right Arrow: Moves a vertical splitter to the right.

    • Up Arrow: Moves a horizontal splitter up.

    • Down Arrow: Moves a horizontal splitter down.

    • Enter: If the primary pane is not collapsed, collapses the pane. If the pane is collapsed, restores the splitter to its previous position.

    • Home (Optional): Moves splitter to the position that gives the primary pane its smallest allowed size. This may completely collapse the primary pane.

    • End (Optional): Moves splitter to the position that gives the primary pane its largest allowed size. This may completely collapse the secondary pane.

    • F6 (Optional): Cycle through window panes.

    Note

    A fixed size splitter omits implementation of the arrow keys.

    WAI-ARIA Roles, States, and Properties

    • The element that serves as the focusable splitter has role separatorarrow-up-right.

    • The separator element has the aria-valuenowarrow-up-right property set to a decimal value representing the current position of the separator.

    • The separator element has the aria-valueminarrow-up-right property set to a decimal value that represents the position where the primary pane has its minimum size. This is typically 0.

    • The separator element has the property set to a decimal value that represents the position where the primary pane has its maximum size. This is typically 100.

    • If the primary pane has a visible label, it is referenced by on the separator element. Otherwise, the separator element has a label provided by .

    • The separator element has referring to the primary pane.

    hashtag
    4. Landmark Regions

    ARIA landmark roles provide a powerful way to identify the organization and structure of a web page. By classifying and labelling sections of a page, they enable structural information that is conveyed visually through layout to be represented programmatically. Screen readers exploit landmark roles to provide keyboard navigation to important sections of a page. Landmark regions can also be used as targets for "skip links" and by browser extensions to enhanced keyboard navigation.

    This section explains how HTML sectioning elements and ARIA landmark roles are used to make it easy for assistive technology users to understand the meaning of the layout of a page.

    hashtag
    4.1 HTML Sectioning Elements

    Several HTML sectioning elements automatically create ARIA landmark regions. So, in order to provide assistive technology users with a logical view of a page, it is important to understand the effects of using HTML sectioning elements. [HTML-ARIAarrow-up-right] contains more information on HTML element role mapping.

    Default landmark roles for HTML sectioning elements

    HTML Element
    Default Landmark Role

    aside

    complementary

    footer

    contentinfo when in context of the body element

    header

    banner when in context of the body element

    main

    main

    nav

    navigation

    hashtag
    4.2 General Principles of Landmark Design

    Including all perceivable content on a page in one of its landmark regions and giving each landmark region a semantically meaningful role is one of the most effective ways of ensuring assistive technology users will not overlook information that is relevant to their needs.

    Step 1: Identify the logical structure

    • Break the page into perceivable areas of content which designers typically indicate visually using alignment and spacing.

    • Areas can be further defined into logical sub-areas as needed.

    • An example of a sub-area is a portlet in a portal application.

    Step 2: Assign landmark roles to each area

    • Assign landmark roles based on the type of content in the area.

    • banner, main, complementary and contentinfo landmarks should be top level landmarks.

    • Landmark roles can be nested to identify parent/child relationships of the information being presented.

    Step 3: Label areas

    • If a specific landmark role is used more than once on a page, provide each instance of that landmark with a unique label. There is one rare circumstance where providing the same label to multiple instances of a landmark can be beneficial: the content and purpose of each instance is identical. For example, a large search results table has two sets of identical pagination controls -- one above and one below the table, so each set is in a navigation region labelled Search Results. In this case, adding extra information to the label that distinguishes the two instances may be more distracting than helpful.

    • If a landmark is only used once on the page it may not require a label. See Landmark Roles section below.

    • If an area begins with a heading element (e.g. h1-h6) it can be used as the label for the area using the aria-labelledby attribute.

    • If an area requires a label and does not have a heading element, provide a label using the aria-label attribute.

    • Do not use the landmark role as part of the label. For example, a navigation landmark with a label "Site Navigation" will be announced by a screen reader as "Site Navigation Navigation". The label should simply be "Site".

    hashtag
    4.3 Landmark Roles

    4.3.2 Complementary

    A complementaryarrow-up-right landmark is a supporting section of the document, designed to be complementary to the main content at a similar level in the DOM hierarchy, but remains meaningful when separated from the main content.

    • complementary landmarks should be top level landmarks (e.g. not contained within any other landmarks).

    • If the complementary content is not related to the main content, a more general role should be assigned (e.g. region).

    • If a page includes more than one complementary landmark, each should have a unique label (see above).

    HTML Technique

    Use the HTML aside element to define a complementary landmark.

    ARIA Technique

    If the HTML aside element technique is not being used, use a role="complementary" attribute to define a complementary landmark.

    Examples

    Complementary Landmark Examplearrow-up-right

    4.3.3 Contentinfo

    A contentinfoarrow-up-right landmark is a way to identify common information at the bottom of each page within a website, typically called the "footer" of the page, including information such as copyrights and links to privacy and accessibility statements.

    • Each page may have one contentinfo landmark.

    • The contentinfo landmark should be a top-level landmark.

    • When a page contains nested document and/or application roles (e.g. typically through the use of iframe and frame elements), each document or application role may have one contentinfo landmark.

    • If a page includes more than one contentinfo landmark, each should have a unique label (see above).

    HTML Techniques

    • The HTML footer element defines a contentinfo landmark when its context is the body element.

    • The HTML footer element is not considered a contentinfo landmark when it is descendant of any of following elements (see HTML Accessibility Mappingsarrow-up-right [HTML-AAMarrow-up-right]):

      • article

      • aside

      • main

    ARIA Technique

    If the HTML footer element technique is not being used, a role="contentinfo" attribute should be used to define a contentinfo landmark.

    Examples

    Contentinfo Landmark Examplearrow-up-right

    4.3.4 Form

    A formarrow-up-right landmark identifies a region that contains a collection of items and objects that, as a whole, combine to create a form when no other named landmark is appropriate (e.g. main or search).

    • Use the search landmark instead of the form landmark when the form is used for search functionality.

    • A form landmark should have a label to help users understand the purpose of the form.

    • A label for the form landmark should be visible to all users (e.g. an h1-h6 element).

    • If a page includes more than one form landmark, each should have a unique label (see above).

    • Whenever possible, controls contained in a form landmark in an HTML document should use native host semantics:

      • button

      • input

    HTML Techniques

    The HTML form element defines a form landmark when it has an accessible name (e.g. aria-labelledby, aria-label or title).

    ARIA Technique

    Use the role="form" to identify a region of the page; do not use it to identify every form field.

    Examples

    Form Landmark Examplearrow-up-right

    4.3.5 Main

    A mainarrow-up-right landmark identifies the primary content of the page.

    • Each page should have one main landmark.

    • The main landmark should be a top-level landmark.

    • When a page contains nested document and/or application roles (e.g. typically through the use of iframe and frame elements), each document or application role may have one main landmark.

    • If a page includes more than one main landmark, each should have a unique label (see above).

    HTML Technique

    Use the HTML main element to define a main landmark.

    ARIA Technique

    If the HTML main element technique is not being used, use a role="main" attribute to define a main landmark.

    Examples

    Main Landmark Examplearrow-up-right

    4.3.6 Navigation

    Navigationarrow-up-right landmarks provide a way to identify groups (e.g. lists) of links that are intended to be used for website or page content navigation.

    • If a page includes more than one navigation landmark, each should have a unique label (see Step 3arrow-up-right above).

    • If a navigation landmark has an identical set of links as another navigation landmark on the page, use the same label for each navigation landmark.

    HTML Technique

    Use the HTML nav element to define a navigation landmark.

    ARIA Technique

    If the HTML nav element technique is not being used, use a role="navigation" attribute to define a navigation landmark.

    Examples

    Navigation Landmark Examplearrow-up-right

    4.3.7 Region

    A regionarrow-up-right landmark is a perceivable section of the page containing content that is sufficiently important for users to be able to navigate to the section.

    • A region landmark must have a label.

    • If a page includes more than one region landmark, each should have a unique label (see Step 3arrow-up-right above).

    • The region landmark can be used identify content that named landmarks do not appropriately describe.

    HTML Technique

    The HTML section element defines a region landmark when it has an accessible name (e.g. aria-labelledby, aria-label or title).

    ARIA Technique

    If the HTML section element technique is not being used, use a role="region" attribute to define a region landmark.

    Examples

    Region Landmark Examplearrow-up-right

    4.3.8 Search

    A searcharrow-up-right landmark contains a collection of items and objects that, as a whole, combine to create search functionality.

    • Use the search landmark instead of the form landmark when the form is used for search functionality.

    • If a page includes more than one search landmark, each should have a unique label (see Step 3arrow-up-right above).

    HTML Technique

    There is no HTML element that defines a search landmark.

    ARIA Technique

    The role="search" attribute defines a search landmark.

    Examples

    Search Landmark Examplearrow-up-right

    hashtag
    5. Providing Accessible Names and Descriptions

    Providing elements with accessible names, and where appropriate, accessible descriptions is one of the most important responsibilities authors have when developing accessible web experiences. While doing so is straightforward for most elements, technical mistakes that can completely block users of assistive technologies are easy to make and unfortunately common. To help authors effectively provide accessible names and descriptions, this section explains their purpose, when authors need to provide them, how browsers assemble them, and rules for coding and composing them. It also guides authors in the use of the following naming and describing techniques and WAI-ARIA properties:

    • Naming:

      • Naming with child content.

      • Naming with a string attribute via aria-label.

      • Naming by referencing content with aria-labelledby.

      • Naming form controls with the label element.

      • Naming fieldsets with the legend element.

      • Naming tables and figures with captions.

      • Fallback names derived from titles and placeholders.

    • Describing:

      • Describing by referencing content with aria-describedby.

      • Describing tables and figures with captions.

    hashtag
    5.1 What ARE Accessible Names and Descriptions?

    An accessible name is a short string, typically 1 to 3 words, that authors associate with an element to provide users of assistive technologies with a label for the element. For example, an input field might have an accessible name of "User ID" or a button might be named "Submit".

    An accessible name serves two primary purposes for users of assistive technologies, such as screen readers:

    1. Convey the purpose or intent of the element.

    2. Distinguish the element from other elements on the page.

    Both the WAI-ARIA specification and WCAG require all focusable, interactive elements to have an accessible name. In addition dialogs and some structural containers, such as tablesarrow-up-right and regionsarrow-up-right, are required to have a name. Many other elements can be named, but whether a name will enhance the accessible experience is determined by various characteristics of the surrounding context. Finally, there are some elements where providing an accessible name is technically possible but not advisable. The Accessible Name Guidance by Rolearrow-up-right section lists naming requirements and guidelines for every ARIA role.

    An accessible description is also an author-provided string that is rendered by assistive technologies. Authors supply a description when there is a need to associate additional information with an element, such as instructions or format requirements for an input field.

    assistive technologies present names differently from descriptions. For instance, screen readers typically announce the name and role of an element first, e.g., a button named Mute Conversationcould be spoken as Mute Conversation button. If an element has a state, it could be announced either before or after the name and role; after name and role is the typical default. For example, a switch button named Mute Conversation in the off state could be announced as Mute Conversation switch button off. Because descriptions are optional strings that are usually significantly longer than names, they are presented last, sometimes after a slight delay. For example, Mute Conversation Switch button off, Silences alerts and notifications about activity in this conversation. To reduce verbosity, some screen readers do not announce descriptions by default but instead inform users of their presence so that users can press a key that will announce the description.

    hashtag
    5.2 How Are Name and Description Strings Derived?

    Because there are several elements and attributes for specifying text to include in an accessible name or description string, and because authors can combine them in a practically endless number of ways, browsers implement fairly complex algorithms for assembling the strings. The sections on accessible name calculationarrow-up-right and accessible description calculationarrow-up-right explain the algorithms and how they implement precedence. However, most authors do not need such detailed understanding of the algorithms since nearly all circumstances where a name or description is useful are supported by the coding patterns described in the naming techniquesarrow-up-right and describing techniquesarrow-up-right sections.

    hashtag
    5.3 Accessible Names

    5.3.1 Cardinal Rules of Naming

    5.3.1.1 Rule 1: Heed Warnings and Test Thoroughly

    Several of the naming techniquesarrow-up-right below include notes that warn against specific coding patterns that are either prohibited by the ARIA specification or fall into gray space that is not yet fully specified. Some of these prohibited or ambiguous patterns may appear logical and even yield desired names in some browsers. However, it is unlikely they will provide consistent results across browsers, especially over time as work to improve the consistency of name calculation across browsers progresses.

    In addition to heeding the warnings provided in the naming techniques, it is difficult to over emphasize the importance of testing to ensure that names browsers calculate match expectations.

    5.3.1.2 Rule 2: Prefer Visible Text

    When a user interface includes visible text that could be used to provide an appropriate accessible name, using the visible text for the accessible name simplifies maintenance, prevents bugs, and reduces language translation requirements. When names are generated from text that exists only in markup and is never displayed visually, there is a greater likelihood that accessible names will not be updated when the user interface design or content are changed.

    If an interactive element, such as an input field or button, does not have a visually persistent text label, consider adjusting the design to include one. In addition to serving as a more robust source for an accessible name, visible text labels enhance accessibility for many people with disabilities who do not use assistive technologies that present invisible accessible names. In most circumstances, visible text labels also make the user interface easier to understand for all users.

    5.3.1.3 Rule 3: Prefer Native Techniques

    In HTML documents, whenever possible, rely on HTML naming techniques, such as the HTML label element for form elements and caption element for tables. While less flexible, their simplicity and reliance on visible text help ensure robust accessible experiences. Several of the naming techniquesarrow-up-right highlight specific accessibility advantages of using HTML features instead of ARIA attributes.

    5.3.1.4 Rule 4: Avoid Browser Fallback

    When authors do not specify an accessible name using an element or attribute that is intended for naming, browsers attempt to help assistive technology users by resorting to fallback methods for generating a name. For example, the HTML title and placeholder attributes are used as last resort sources of content for accessible names. Because the purpose of these attributes is not naming, their content typically yields low quality accessible names that are not effective.

    5.3.1.5 Rule 5: Compose Brief, Useful Names

    Similar to how visually crowded screens and ambiguous icons reduce usability, excessively long, insufficiently distinct, or unclear accessible names can make a user interface very difficult, or even impossible, to use for someone who relies on a non-visual form of the user interface. In other words, for a web experience to be accessible, its accessible names must be effective. The section on Composing Effective and User-friendly Accessible Namesarrow-up-right provides guidance for balancing brevity and clarity.

    5.3.2 Naming Techniques

    5.3.2.1 Naming with Child Content

    Certain elements get their name from the content they contain. For example, the following link is named "Home".

    When assistive technologies render an element that gets its accessible name from its content, such as a link or button, the accessible name is the only content the user can perceive for that element. This is in contrast to other elements, such as text fields or tables, where the accessible name is a label that is presented in addition to the value or content of the element. For instance, the accessible name of a table can be derived from a caption element, and assistive technologies render both the caption and all other content contained inside the table.

    Elements having one of the following roles are, by default, named by a string calculated from their descendant content:

    • button

    • cell

    • checkbox

    • columnheader

    • gridcell

    • heading

    • link

    • menuitem (content contained in a child menu element is excluded.)

    • menuitemcheckbox

    • menuitemradio

    • option

    • radio

    • row

    • rowheader

    • switch

    • tab

    • tooltip

    • treeitem (content included in a child group element is excluded.)

    When calculating a name from content for an element, user agents recursively walk through each of its descendant elements, calculate a name string for each descendant, and concatenate the resulting strings. In two special cases, certain descendants are ignored: group descendants of treeitem elements and menu descendants of menuitem elements are omitted from the calculation. For example, in the following tree, the name of the first tree item is Fruits; Apples, Bananas, and Oranges are omitted.

    Warning

    If an element with one of the above roles that supports naming from child content is named by using aria-label or aria-labelledby, content contained in the element and its descendants is hidden from assistive technology users unless the descendant content is referenced by aria-labelledby. It is strongly recommended to avoid using either of these attributes to override content of one of the above elements except in rare circumstances where hiding content from assistive technology users is beneficial. In addition, in situations where visible content is hidden from assistive technology users by use of one of these attributes, thorough testing with assistive technologies is particularly important.

    5.3.2.2 Naming with a String Attribute Via aria-label

    The aria-labelarrow-up-right property enables authors to name an element with a string that is not visually rendered. For example, the name of the following button is "Close".

    The aria-label property is useful when there is no visible text content that will serve as an appropriate accessible name.

    The aria-label property affects assistive technology users in one of two different ways, depending on the role of the element to which it is applied. When applied to an element with one of the roles that supports naming from child contentarrow-up-right, aria-label hides descendant content from assistive technology users and replaces it with the value of aria-label. However, when applied to nearly any other type of element, assistive technologies will render both the value of aria-label and the content of the element. For example, the name of the following navigation region is "Product".

    When encountering this navigation region, a screen reader user will hear the name and role of the element, e.g., "Product navigation region", and then be able to read through the links contained in the region.

    Warning

    1. If aria-label is applied to an element with one of the roles that supports naming from child contentarrow-up-right, content contained in the element and its descendants is hidden from assistive technology users. It is strongly recommended to avoid using aria-label to override content of one of these elements except in rare circumstances where hiding content from assistive technology users is beneficial.

    2. There are certain types of elements, such as paragraphs and list items, that should not be named with aria-label. They are identified in the table in the Accessible Name Guidance by Rolearrow-up-right section.

    3. Because the value of aria-label is not rendered visually, testing with assistive technologies to ensure the expected name is presented to users is particularly important.

    4. When a user interface is translated into multiple languages, ensure that aria-label values are translated.

    5.3.2.3 Naming with Referenced Content Via aria-labelledby

    The aria-labelledby propertyarrow-up-right enables authors to reference other elements on the page to define an accessible name. For example, the following switch is named by the text content of a previous sibling element.

    Note that while using aria-labelledby is similar in this situation to using an HTML label element with the for attribute, one significant difference is that browsers do not automatically make clicking on the labeling element activate the labeled element; that is an author responsibility. However, HTML label cannot be used to label a span element. Fortunately, an HTML input with type="checkbox" allows the ARIA switch role, so when feasible, using the following approach creates a more robust solution.

    The aria-labelledby property is useful in a wide variety of situations because:

    • It has the highest precedence when browsers calculate accessible names, i.e., it overrides names from child content and all other naming attributes, including aria-label.

    • It can concatenate content from multiple elements into a single name string.

    • It incorporates content from elements regardless of their visibility, i.e., it even includes content from elements with the HTML hidden attribute, CSS display: none, or CSS visibility: hidden in the calculated name string.

    • It incorporates the value of input elements, i.e., if it references a textbox, the value of the textbox is included in the calculated name string.

    An example of referencing a hidden element with aria-labelledby could be a label for a night switch control:

    In some cases, the most effective name for an element is its own content combined with the content of another element. Because aria-labelledby has highest precedence in name calculation, in those situations, it is possible to use aria-labelledby to reference both the element itself and the other element. In the following example, the "Read more..." link is named by the element itself and the article's heading, resulting in a name for the link of "Read more... 7 ways you can help save the bees".

    When multiple elements are referenced by aria-labelledby, text content from each referenced element is concatenated in the order specified in the aria-labelledby value. If an element is referenced more than one time, only the first reference is processed. When concatenating content from multiple elements, browsers trim leading and trailing white space and separate content from each element with a single space.

    In the above example, the accessible name of the button will be "Download PDF, 2.4 MB", with a space between "Download" and "PDF", and not "DownloadPDF, 2.4 MB".

    Warning

    1. The aria-labelledby property cannot be chained, i.e., if an element with aria-labelledby references another element that also has aria-labelledby, the aria-labelledby attribute on the referenced element will be ignored.

    2. If an element is referenced by aria-labelledby more than one time during a name calculation, the second and any subsequent references will be ignored.

    3. There are certain types of elements, such as paragraphs and list items, that should not be named with aria-labelledby. They are identified in the table in the section.

    4. If aria-labelledby is applied to an element with one of the roles that supports , content contained in the element and its descendants is hidden from assistive technology users unless it is also referenced by aria-labelledby. It is strongly recommended to avoid using this attribute to override content of one of these elements except in rare circumstances where hiding content from assistive technology users is beneficial.

    5. Because calculating the name of an element with aria-labelledby can be complex and reference hidden content, testing with assistive technologies to ensure the expected name is presented to users is particularly important.

    5.3.2.4 Naming Form Controls with the Label Element

    The HTML label element enables authors to identify content that serves as a label and associate it with a form control. When a label element is associated with a form control, browsers calculate an accessible name for the form control from the label content.

    For example, text displayed adjacent to a checkbox may be visually associated with the checkbox, so it is understood as the checkbox label by users who can perceive that visual association. However, unless the text is programmatically associated with the checkbox, assistive technology users will experience a checkbox without a label. Wrapping the checkbox and the labeling text in a label element as follows gives the checkbox an accessible name.

    A form control can also be associated with a label by using the for attribute on the label element. This allows the label and the form control to be siblings or have different parents in the DOM, but requires adding an id attribute to the form control, which can be error-prone. When possible, use the above encapsulation technique for association instead of the following for attribute technique.

    Using the label element is an effective technique for satisfying Rule 2: Prefer Visible Textarrow-up-right. It also satisfies Rule 3: Prefer Native Techniquesarrow-up-right. Native HTML labels offer an important usability and accessibility advantage over ARIA labeling techniques: browsers automatically make clicking the label equivalent to clicking the form control. This increases the hit area of the form control.

    5.3.2.6 Naming Tables and Figures with Captions

    The accessible name for HTML table and figure elements can be derived from a child caption or figcaption element, respectively. Tables and figures often have a caption to explain what they are about, how to read them, and sometimes giving them numbers used to refer to them in surrounding prose. Captions can help all users better understand content, but are especially helpful to users of assistive technologies.

    In HTML, the table element marks up a data table, and can be provided with a caption using the caption element. If the table element does not have aria-label or aria-labelledby, then the caption will be used as the accessible name. For example, the accessible name of the following table is Special opening hours.

    The following example gives the table a number (Table 1) so it can be referenced.

    Note: Above table content is from Caloric restriction, the traditional Okinawan diet, and healthy aging: the diet of the world's longest-lived people and its potential impact on morbidity and life spanarrow-up-right.

    If a table is named using aria-label or aria-labelledby, then a caption element, if present, will become an accessible description. For an example, see Describing Tables and Figures with Captionsarrow-up-right.

    Similarly, an HTML figure element can be given a caption using the figcaption element. The caption can appear before or after the figure, but it is more common for figures to have the caption after.

    Like with table elements, if a figure is not named using aria-label or aria-labelledby, the content of the figcaption element will be used as the accessible name. However unlike table elements, if the figcaption element is not used for the name, it does not become an accessible description unless it is referenced by aria-describedby. Nevertheless, assistive technologies will render the content of a figcaption regardless of whether it is used as a name, description, or neither.

    Using the caption element to name a table element, or a figcaption element to name a figure element, satisfies Rule 2: Prefer Visible Textarrow-up-right and Rule 3: Prefer Native Techniquesarrow-up-right.

    5.3.2.7 Fallback Names Derived from Titles and Placeholders

    When an accessible name is not provided using one of the primary techniques (e.g., the aria-label or aria-labelledby attributes), or native markup techniques (e.g., the HTML label element, or the alt attribute of the HTML img element), browsers calculate an accessible name from other attributes as a fallback mechanism. Because the attributes used in fallback name calculation are not intended for naming, they typically yield low quality accessible names that are not effective. So, As advised by Rule 4: Avoid Browser Fallbackarrow-up-right, prefer the explicit labeling techniques described above over fallback techniques described in this section.

    Any HTML element can have a title attribute specified. The title attribute may be used as the element's fallback accessible name. The title attribute is commonly presented visually as a tooltip when the user hovers over the element with a pointing device, which is not particularly discoverable, and is also not accessible to visual users without a pointing device.

    For example, a fieldset element without a legend element child, but with a title attribute, gets its accessible name from the title attribute.

    For the HTML input and textarea elements, the placeholder attribute is used as a fallback labeling mechanism if nothing else (including the title attribute) results in a label. It is better to use a label element, since it does not disappear visually when the user focuses the form control.

    5.3.3 Composing Effective and User-friendly Accessible Names

    For assistive technology users, especially screen reader users, the quality of accessible names is one of the most significant contributors to usability. Names that do not provide enough information reduce users' effectiveness while names that are too long reduce efficiency. And, names that are difficult to understand reduce effectiveness, efficiency, and enjoyment.

    The following guidelines provide a starting point for crafting user friendly names.

    • Convey function or purpose, not form. For example, if an icon that looks like the letter X closes a dialog, name it Close, not X. Similarly, if a set of navigation links in the left side bar navigate among the product pages in a shopping site, name the navigation region Product, not Left.

    • Put the most distinguishing and important words first. Often, for interactive elements that perform an action, this means a verb is the first word. For instance, if a list of contacts displays Edit, Delete, and Actions buttons for each contact, then Edit John Doe, Delete John Doe, and Actions for John Doe would be better accessible names than John Doe edit, John Doe delete, and John Doe actions. By placing the verb first in the name, screen reader users can more easily and quickly distinguish the buttons from one another as well as from the element that opens the contact card for John Doe.

    • Be concise. For many elements, one to three words is sufficient. Only add more words when necessary.

    • Do NOT include a WAI-ARIA role name in the accessible name. For example, do not include the word button in the name of a button, the word image in the name of an image, or the word navigation in the name of a navigation region. Doing so would create duplicate screen reader output since screen readers convey the role of an element in addition to its name.

    • Create unique names for elements with the same role unless the elements are actually identical. For example, ensure every link on a page has a different name except in cases where multiple links reference the same location. Similarly, give every navigation region on a page a different name unless there are regions with identical content that performs identical navigation functions.

    • Start names with a capital letter; it helps some screen readers speak them with appropriate inflection. Do not end names with a period; they are not sentences.

    5.3.4 Accessible Name Guidance by Role

    Certain elements always require a name, others may usually or sometimes require a name, and still others should never be named. The table below lists all ARIA roles and provides the following information for each :

    Necessity of Naming

    Indicates how necessary it is for authors to add a naming attribute or element to supplement or override the content of an element with the specified role. This column may include one of the following values:

    • Required Only If Content Insufficient: An element with this role is named by its descendant content. If aria-label or aria-labelledby is applied, content contained in the element and its descendants is hidden from assistive technology users unless it is also referenced by aria-labelledby. Avoid hiding descendant content except in the rare circumstances where doing so benefits assistive technology users.

    • Required: The ARIA specification requires authors to provide a name; a missing name triggers accessibility validators to flag a violation.

    • Recommended: Providing a name is strongly recommended.

    • Discretionary: Naming is either optional or, in the circumstances described in the guidance column, is discouraged.

    • Do Not Name: Naming is strongly discouraged even if it is technically permitted; often assistive technologies do not render a name even if provided.

    Guidance:

    Provides information to help determine if providing a name is beneficial, and if so, describes any recommended techniques.

    role
    Necessity of Naming
    Guidance

    Discretionary

    Some screen readers announce the name of an alert before announcing the content of the alert. Thus, aria-label provides a method for prefacing the visible content of an alert with text that is not displayed as part of the alert. Using aria-label is functionally equivalent to providing off-screen text in the contents of the alert, except off-screen text would be announced by screen readers that do not support aria-label on alert elements.

    Required

    Use aria-labelledby if a visible label is present, otherwise use aria-label.

    Required

    Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • Recommended to distinguish articles from one another; helps users when navigating among articles.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    | | bannerarrow-up-right | Discretionary |

    • Necessary in the uncommon circumstance where two banner landmark regions are present on the same page. It is otherwise optional.

    • Named using aria-labelledby if a visible label is present, otherwise with aria-label.

    • See the Banner Landmarkarrow-up-right section.

    | | buttonarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    | | cellarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • Note that a name is not required; assistive technologies expect an empty cell in a table to be represented by an empty name.

    • Note that associated row or column headers do not name a cell; the name of a cell in a table is its content. Headers are complementary information.

    | | checkboxarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • If based on HTML type="checkbox", use a label element.

    • Otherwise, reference visible content via aria-labelledby.

    | | columnheaderarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • If the columnheader role is implied from an HTML th, the HTML abbr attribute can be used to specify an abbreviated version of the name that is only announced when screen readers are reading an associated cell within the table, grid, or treegrid.

    | | comboboxarrow-up-right | Required |

    • If the combobox role is applied to an HTML select or input element, can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    | | complementaryarrow-up-right | Recommended |

    • Naming is necessary when two complementary landmark regions are present on the same page.

    • Naming is recommended even when one complementary region is present to help users understand the purpose of the region's content when navigating among landmark regions.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the section.

    | | contentinfoarrow-up-right | Discretionary |

    • Necessary in the uncommon circumstance where two contentinfo landmark regions are present on the same page. It is otherwise optional.

    • Named using aria-labelledby if a visible label is present, otherwise with aria-label.

    | | definitionarrow-up-right | Recommended | Reference the term being defined with role="term", using aria-labelledby. | | dialogarrow-up-right | Required | Use aria-labelledby if a visible label is present, otherwise use aria-label. | | directoryarrow-up-right | Discretionary |

    • Naming can help users understand the purpose of the directory.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    | | documentarrow-up-right | Discretionary | Elements with the document role are contained within an element with the application role, which is required to have a name. Typically, the name of the application element will provide sufficient context and identity for the document element. Because the application element is used only to create unusual, custom widgets, careful assessment is necessary to determine whether or not adding an accessible name is beneficial. | | feedarrow-up-right | Recommended |

    • Helps screen reader users understand the context and purpose of the feed.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the Feed Design Patternarrow-up-right.

    | | figurearrow-up-right | Recommended |

    • For HTML, use the figure and figcaption elements. The figcaption will serve as the accessible name for the figure. See the Naming Tables and Figures with Captionsarrow-up-right section.

    • When not using HTML, or when retrofitting legacy HTML, use the aria-labelledby on the figure, pointing to the figure's caption.

    • If there is no visible caption, aria-label can be used.

    | | formarrow-up-right | Recommended |

    • Helps screen reader users understand the context and purpose of the form landmark.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the Form Landmarkarrow-up-right section.

    | | gridarrow-up-right | Required |

    • If the grid is applied to an HTML table element, then the accessible name can be derived from the table's caption element.

    • Otherwise, use aria-labelledby if a visible label is present, otherwise use aria-label.

    | | gridcellarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • Note that a name is not required; assistive technologies expect an empty cell in a grid to be represented by an empty name.

    • Note that associated row or column headers do not name a gridcell; the name of a cell in a grid is its content. Headers are complementary information.

    | | grouparrow-up-right | Discretionary |

    • When using the HTML fieldset element, the accessible name can be derived from the legend element.

    • When using the HTML details element, do not provide an accessible name for this element. The user interacts with the summary element, and that can derive its accessible name from its contents.

    • When using the HTML optgroup element, use the label attribute.

    • Otherwise, use aria-labelledby if a visible label is present, otherwise use aria-label.

    | | headingarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    | | imgarrow-up-right | Required | For the HTML img element, use the alt attribute. For other elements with the img role, use aria-labelledby or aria-label. | | linkarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    | | listarrow-up-right | Discretionary |

    • Potentially beneficial for users of screen readers that support both list names and navigation among lists on a page.

    • Potentially a source of distracting or undesirable screen reader verbosity, especially if nested within a named container, such as a navigation region.

    • Can be named using aria-labelledby if a visible label is present, otherwise with aria-label.

    | | listboxarrow-up-right | Required |

    • If the listbox role is applied to an HTML select element (with the multiple attribute or a size attribute having a value greater than 1), can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    • See the .

    | | listitemarrow-up-right | Do Not Name | Not supported by assistive technologies; it is necessary to include relevant content within the list item. | | logarrow-up-right | Required | Use aria-labelledby if a visible label is present, otherwise use aria-label. | | mainarrow-up-right | Discretionary |

    • Potentially helpful for orienting assistive technology users, especially in single-page applications where main content changes happen without generating a page load event.

    • Can be named using aria-labelledby if a visible label is present, otherwise with aria-label.

    • See the section.

    | | marqueearrow-up-right | Required | Use aria-labelledby if a visible label is present, otherwise use aria-label. | | matharrow-up-right | Recommended |

    • If the math element has only presentational children and the accessible name is intended to convey the mathematical expression, use aria-label to provide a string that represents the expression.

    • If the math element contains navigable content that conveys the mathematical expression and a visible label for the expression is present, use aria-labelledby.

    • Otherwise, use aaria-label to name the expression, e.g., aria-label="Pythagorean Theorem".

    | | menuarrow-up-right | Recommended |

    • Use aria-labelledby to refer to the menuitem or button that controls this element's display.

    • Otherwise, use aria-label.

    • See the Menu or Menu bar Design Patternarrow-up-right.

    | | menubararrow-up-right | Recommended |

    • Helps screen reader users understand the context and purpose of menuitem elements in a menubar. Naming a menubar is comparable to naming a menu button. The name of a button that opens a menu conveys the purpose of the menu it opens. Since a menubar element is displayed persistently, a name on the menubar can serve that same purpose.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the .

    | | menuitemarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • Note: content contained within a child menu is automatically excluded from the accessible name calculation.

    • See the .

    | | menuitemcheckboxarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • See the Menu or Menu bar Design Patternarrow-up-right.

    | | menuitemradioarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • See the Menu or Menu bar Design Patternarrow-up-right.

    | | navigationarrow-up-right | Recommended |

    • Helps screen reader users understand the context and purpose of the navigation landmark.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the Navigation Landmarkarrow-up-right section.

    | | nonearrow-up-right | Do Not Name | An element with role="none" is not part of the accessibility tree (except in error cases). Do not use aria-labelledby or aria-label. | | notearrow-up-right | Discretionary |

    • Naming is optional, but can help screen reader users understand the context and purpose of the note.

    • Named using aria-labelledby if a visible label is present, otherwise with aria-label.

    | | optionarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • See the Combo Box Design Patternarrow-up-right.

    | | presentationarrow-up-right | Do Not Name | An element with role="presentation" is not part of the accessibility tree (except in error cases). Do not use aria-labelledby or aria-label. | | progressbararrow-up-right | Required |

    • If the progressbar role is applied to an HTML progress element, can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    | | radioarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • If based on HTML type="checkbox", use a label element.

    • Otherwise, reference visible content via aria-labelledby.

    | | radiogrouparrow-up-right | Required |

    • Recommended to help assistive technology users understand the purpose of the group of radio buttons.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the .

    | | regionarrow-up-right | Required |

    • Helps screen reader users understand the context and purpose of the landmark.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the Region Landmarkarrow-up-right section.

    | | rowarrow-up-right | Required Only If Content Insufficient AND descendant of a treegrid AND the row is focusable | When row elements are focusable in a treegridarrow-up-right, screen readers announce the entire contents of a row when navigating by row. This is typically the most appropriate behavior. However, in some circumstances, it could be beneficial to change the order in which cells are announced or exclude announcement of certain cells by using aria-labelledby to specify which cells to announce. | | rowgrouparrow-up-right | Do Not Name | Not supported by assistive technologies. | | rowheaderarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • If the rowheader role is implied from an HTML th, the HTML abbr attribute can be used to specify an abbreviated version of the name that is only announced when screen readers are reading an associated cell within the table, grid, or treegrid.

    | | scrollbararrow-up-right | Discretionary |

    • Naming is optional, but can potentially help screen reader users understand the purpose of the scrollbar. The purpose is also conveyed using the aria-controls attribute, which is required for scrollbar.

    • Named using aria-labelledby if a visible label is present, otherwise with aria-label.

    | | searcharrow-up-right | Recommended |

    • Helps screen reader users understand the context and purpose of the search landmark.

    • Named using aria-labelledby if a visible label is present, otherwise with aria-label.

    • See the Search Landmarkarrow-up-right section.

    | | searchboxarrow-up-right | Required |

    • If the searchbox role is applied to an HTML input element, can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    | | separatorarrow-up-right | Discretionary |

    • Recommended if there is more than one focusable separator element on the page.

    • Can help assistive technology users understand the purpose of the separator.

    • Named using aria-labelledby if a visible label is present, otherwise with aria-label.

    | | sliderarrow-up-right | Required |

    • If the slider role is applied to an HTML input element, can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    • See the and the .

    | | spinbuttonarrow-up-right | Required |

    • If the textbox role is applied to an HTML input element, can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    • See the .

    | | statusarrow-up-right | Discretionary | Some screen readers announce the name of a status element before announcing the content of the status element. Thus, aria-label provides a method for prefacing the visible content of a status element with text that is not displayed as part of the status element. Using aria-label is functionally equivalent to providing off-screen text in the contents of the status element, except off-screen text would be announced by screen readers that do not support aria-label on status elements. | | switcharrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • If based on HTML type="checkbox", use a label element.

    • Otherwise, reference visible content via aria-labelledby.

    | | tabarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    | | tablearrow-up-right | Required |

    • If using HTML table element, use the caption element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    • See the .

    | | tablistarrow-up-right | Recommended |

    • Helps screen reader users understand the context and purpose of the tablist.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the Carousel Design Patternarrow-up-right and .

    | | tabpanelarrow-up-right | Required |

    • Use aria-labelledby pointing to the tab element that controls the tabpanel.

    • See the Carousel Design Patternarrow-up-right and Tabs Design Patternarrow-up-right.

    | | termarrow-up-right | Do Not Name | Since a term is usually the name for the role="definition" element, it could be confusing if the term itself also has a name. | | textboxarrow-up-right | Required |

    • If the textbox role is applied to an HTML input or textarea element, can be named with an HTML label element.

    • Otherwise use aria-labelledby if a visible label is present.

    • Use aria-label if a visible label is not present.

    | | timerarrow-up-right | Required | Use aria-labelledby if a visible label is present, otherwise use aria-label. | | toolbararrow-up-right | Recommended |

    • If there is more than one toolbar element on the page, naming is required.

    • Helps assistive technology users to understand the purpose of the toolbar, even when there is only one toolbar on the page.

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the .

    | | tooltiparrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    | | treearrow-up-right | Required |

    • Use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the Tree View Design Patternarrow-up-right.

    | | treegridarrow-up-right | Required |

    • If the treegrid is applied to an HTML table element, then the accessible name can be derived from the table's caption element.

    • Otherwise, use aria-labelledby if a visible label is present, otherwise use aria-label.

    • See the .

    | | treeitemarrow-up-right | Required Only If Content Insufficient |

    • Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive technologies.

    • Ideally named by visible, descendant content.

    • Note: content contained within a child group is automatically excluded from the accessible name calculation.

    • See the .

    |

    5.3.5 Accessible name calculation

    User agents construct an accessible name string for an element by walking through a list of potential naming methods and using the first that generates a name. The algorithm they follow is defined in the accessible name specificationarrow-up-right. It is roughly like the following:

    1. The aria-labelledby property is used if present.

    2. If the name is still empty, the aria-label property is used if present.

    3. If the name is still empty, then host-language-specific attributes or elements are used if present. For HTML, these are, depending on the element:

      input whose type attribute is in the Button, Submit Button, or Reset Button state

      The value attribute.

      input whose type attribute is in the Image Button state

      img

      area

      The alt attribute.

      fieldset

      The first child legend element.

      Other form elements

      The associated label element(s).

      figure

      The first child figcaption element.

      table

      The first child caption element.

    4. If the name is still empty, then for elements with a role that supports naming from child content, the content of the element is used.

    5. Finally, if the name is still empty, then other fallback host-language-specific attributes or elements are used if present. For HTML, these are, depending on the element:

      input whose type attribute is in the Text, Password, Search, Telephone, or URL states

      textarea

      The title attribute. Otherwise, the placeholder

    The final step is a fallback mechanism. Generally when labeling an element, use one of the non-fallback mechanisms.

    When calculating a name from content, the user agent walks through all descendant nodes except in the cases of treeitem and menuitem as described below. And, when following references in an aria-labelledby attribute, it similarly walks the tree of each referenced element. Thus, the naming algorithm is recursive. The following two sections explain non-recursive and recursive examples of how the algorithm works.

    When calculating a name from content for the treeitem role, descendant content of child group elements are not included. For example, in the following tree, the name of the first tree item is Fruits; Apples, Bananas, and Oranges are automatically omitted.

    Similarly, when calculating a name from content for the menuitem role, descendant content of child menu elements are not included. So, the name of the first parent menuitem in the following menu is Fruits.

    5.3.5.1 Examples of non-recursive accessible name calculation

    Consider an input element that has no associated label element and only a name attribute and so does not have an accessible name (do not do this):

    If there is a placeholder attribute, then it serves as a naming fallback mechanism (avoid doing this):

    If there is also a title attribute, then it is used as the accessible name instead of placeholder, but it is still a fallback (avoid doing this):

    If there is also a label element (recommended), then that is used as the accessible name, and the title attribute is instead used as the accessible description:

    If there is also an aria-label attribute (not recommended unless it adds clarity for assistive technology users), then that becomes the accessible name, overriding the label element:

    If there is also an aria-labelledby attribute, that wins over the other elements and attributes (the aria-label attribute ought to be removed if it is not used):

    5.3.5.2 Examples of recursive accessible name calculation

    The accessible name calculation algorithm will be invoked recursively when necessary. An aria-labelledby reference causes the algorithm to be invoked recursively, and when computing an accessible name from content the algorithm is invoked recursively for each child node.

    In this example, the label for the button is computed by recursing into each child node, resulting in Move to trash.

    When following an aria-labelledby reference, the algorithm avoids following the same reference twice to avoid infinite loops.

    In this example, the label for the button is computed by first following the aria-labelledby reference to the parent element, and then computing the label for that element from the child nodes, first visiting the button element again but ignoring the aria-labelledby reference and instead using the aria-label, and then visiting the next child (the text node). The resulting label is Remove meeting: Daily status report.

    hashtag
    5.4 Accessible Descriptions

    5.4.1 Describing Techniques

    5.4.1.1 Describing by referencing content with aria-describedby

    The aria-describedby property works similarly to the aria-labelledby property. For example, a button could be described by a sibling paragraph.

    Descriptions are reduced to text strings. For example, if the description contains an HTML img element, a text equivalent of the image is computed.

    As with aria-labelledby, it is possible to reference an element using aria-describedby even if that element is hidden. For example, a text field in a form could have a description that is hidden by default, but can be revealed on request using a disclosure widget. The description could also be referenced from the text field directly with aria-describedby. In the following example, the accessible description for the input element is Your username is the name that you use to log in to this service.

    5.4.1.2 Describing Tables and Figures with Captions

    In HTML, if the table is named using aria-label or aria-labelledby, a child caption element becomes an accessible description. For example, a preceding heading might serve as an appropriate accessible name, and the caption element might contain a longer description. In such a situation, aria-labelledby could be used on the table to set the accessible name to the heading content and the caption would become the accessible description.

    The HTML figure element can get its accessible name from its figcaption element, but it will not be used as the accessible description, even if it was not used as the accessible name. If the figcaption element is appropriate as an accessible description, and the accessible name is set using aria-labelledby or aria-label, then the figcaption can be explicitly set as the accessible description using the aria-describedby attribute.

    5.4.1.3 Descriptions Derived from Titles

    If an accessible description was not provided using the aria-describedby attribute or one of the primary host-language-specific attributes or elements (e.g., the caption element for table), then, for HTML, if the element has a title attribute, that is used as the accessible description.

    A visible description together with aria-describedby is generally recommended. If a description that is not visible is desired, then the title attribute can be used, for any HTML element that can have an accessible description.

    Note that the title attribute might not be accessible to some users, in particular sighted users not using a screen reader and not using a pointing device that supports hover (e.g., a mouse).

    For example, an input element with input constrained using the pattern attribute can use the title attribute to describe what the expected input is.

    The title attribute in this case can be shown to the user as a tooltip when the user hovers or focuses the control, but also as part of the error message when the user agent validates the form, if the input element's value doesn't match the pattern.

    As another example, a link can use the title attribute to describe the link in more detail.

    5.4.2 Accessible description calculation

    Like the accessible name calculationarrow-up-right, the accessible description calculation produces a text string.

    The accessible description calculation algorithm is the same as the accessible name calculation algorithm except for a few branch points that depend on whether a name or description is being calculated. In particular, when accumulating text for an accessible description, the algorithm uses aria-describedby instead of aria-labelledby.

    User agents construct an accessible description string for an element by walking through a list of potential description methods and using the first that generates a description. The algorithm they follow is defined in the accessible name specificationarrow-up-right. It is roughly like the following:

    1. The aria-describedby property is used if present.

    2. If the description is still empty, then host-language-specific attributes or elements are used if present, if it wasn't already used as the accessible name. For HTML, these are, depending on the element:

      input whose type attribute is in the Button, Submit Button, or Reset Button state

      The value attribute.

      summary

      The element's subtree.

      table

      The first child caption element.

    3. Finally, if the description is still empty, then other host-language-specific attributes or elements are used if present, if it wasn't already used for the accessible name. For HTML, this is the title attribute.

    hashtag
    6. Developing a Keyboard Interface

    Unlike native HTML form elements, browsers do not provide keyboard support for graphical user interface (GUI) components that are made accessible with ARIA; authors have to provide the keyboard support in their code. This section describes the principles and methods for making the functionality of a web page that includes ARIA widgets, such as menus and grids, as well as interactive components, such as toolbars and dialogs, operable with a keyboard. Along with the basics of focus management, this section offers guidance toward the objective of providing experiences to people who rely on a keyboard that are as efficient and enjoyable as the experiences available to others.

    This section covers:

    1. Understanding fundamental principles of focus movement conventions used in ARIA design patterns.

    2. Maintaining visible focus, predictable focus movement, and distinguishing between keyboard focus and the selected state.

    3. Managing movement of keyboard focus between components, e.g., how the focus moves when the Tab and Shift+Tab keys are pressed.

    4. Managing movement of keyboard focus inside components that contain multiple focusable elements, e.g., two different methods for programmatically exposing focus inside widgets like radio groups, menus, listboxes, trees, and grids.

    5. Determining when to make disabled interactive elements focusable.

    6. Assigning and revealing keyboard shortcuts, including guidance on how to avoid problematic conflicts with keyboard commands of assistive technologies, browsers, and operating systems.

    hashtag
    6.1 Fundamental Keyboard Navigation Conventions

    ARIA roles, states, and properties model accessibility behaviors and features shared among GUI components of popular desktop GUIs, including Microsoft Windows, macOS, and GNOME. Similarly, ARIA design patterns borrow user expectations and keyboard conventions from those platforms, consistently incorporating common conventions with the aim of facilitating easy learning and efficient operation of keyboard interfaces across the web.

    For a web page to be accessible, all interactive elements must be operable via the keyboard. In addition, consistent application of the common GUI keyboard interface conventions described in the ARIA design patternsarrow-up-right is important, especially for assistive technology users. Consider, for example, a screen reader user operating a tree. Just as familiar visual styling helps users discover how to expand a tree branch with a mouse, ARIA attributes give the tree the sound and feel of a tree in a desktop application. So, screen reader users will commonly expect that pressing the right arrow key will expand a collapsed node. Because the screen reader knows the element is a tree, it also has the ability to instruct a novice user how to operate it. Similarly, voice recognition software can implement commands for expanding and collapsing branches because it recognizes the element as a tree and can execute appropriate keyboard commands. All this is only possible if the tree implements the GUI keyboard conventions as described in the ARIA tree patternarrow-up-right.

    A primary keyboard navigation convention common across all platforms is that the tab and shift+tab keys move focus from one UI component to another while other keys, primarily the arrow keys, move focus inside of components that include multiple focusable elements. The path that the focus follows when pressing the tab key is known as the tab sequence or tab ring.

    Common examples of UI components that contain multiple focusable elements are radio groups, tablists, menus, and grids. A radio group, for example, contains multiple radio buttons, each of which is focusable. However, only one of the radio buttons is included in the tab sequence. After pressing the Tab key moves focus to a radio button in the group, pressing arrow keys moves focus among the radio buttons in the group, and pressing the Tab key moves focus out of the radio group to the next element in the tab sequence.

    The ARIA specification refers to a discrete UI component that contains multiple focusable elements as a compositearrow-up-right widget. The process of controlling focus movement inside a composite is called managing focus. Following are some ARIA design patterns with example implementations that demonstrate focus management:

    • Comboboxarrow-up-right

    • Gridarrow-up-right

    • Listboxarrow-up-right

    • Tree Grid

    hashtag
    6.2 Discernible and Predictable Keyboard Focus

    Work to complete this section is tracked by issue 217.arrow-up-right

    When operating with a keyboard, two essentials of a good experience are the abilities to easily discern the location of the keyboard focus and to discover where focus landed after a navigation key has been pressed. The following factors affect to what extent a web page affords users these capabilities.

    1. Visibility of the focus indicator: Users need to be able to easily distinguish the keyboard focus indicator from other features of the visual design. Just as a mouse user may move the mouse to help find the mouse pointer, a keyboard user may press a navigation key to watch for movement. If visual changes in response to focus movement are subtle, many users will lose track of focus and be unable to operate. Authors are advised to rely on the default focus indicators provided by browsers. If overriding the default, consider:

      • something about ... Colors and gradients can disappear in high contrast modes.

      • Users need to be able to easily distinguish between focus and selection as described in , especially when a component that contains selected elements does not contain the focus.

      • ... other considerations to be added ...

    2. Persistence of focus: It is essential that there is always a component within the user interface that is active (document.activeElement is not null or is not the body element) and that the active element has a visual focus indicator. Authors need to manage events that effect the currently active element so focus remains visible and moves logically. For example, if the user closes a dialog or performs a destructive operation like deleting an item from a list, the active element may be hidden or removed from the DOM. If such events are not managed to set focus on the button that triggered the dialog or on the list item following the deleted item, browsers move focus to the body element, effectively causing a loss of focus within the user interface.

    3. Predictability of movement: Usability of a keyboard interface is heavily influenced by how readily users can guess where focus will land after a navigation key is pressed. Some possible approaches to optimizing predictability include:

      • Move focus in a pattern that matches the reading order of the page's language. In left to right languages, for example, create a tab sequence that moves focus left to right and then top to bottom.

      • Incorporate all elements of a section of the page in the tab sequence before moving focus to another section. For instance, in a page with multiple columns that has content in a left side bar, center region, and right side bar, build a tab sequence that covers all elements in the left sidebar before focus moves to the first focusable element in the center column.

    hashtag
    6.3 Focus VS Selection and the Perception of Dual Focus

    Occasionally, it may appear as if two elements on the page have focus at the same time. For example, in a multi-select list box, when an option is selected it may be greyed. Yet, the focus indicator can still be moved to other options, which may also be selected. Similarly, when a user activates a tab in a tablist, the selected state is set on the tab and its visual appearance changes. However, the user can still navigate, moving the focus indicator elsewhere on the page while the tab retains its selected appearance and state.

    Focus and selection are quite different. From the keyboard user's perspective, focus is a pointer, like a mouse pointer; it tracks the path of navigation. There is only one point of focus at any time and all operations take place at the point of focus. On the other hand, selection is an operation that can be performed in some widgets, such as list boxes, trees, and tablists. If a widget supports only single selection, then only one item can be selected and very often the selected state will simply follow the focus when focus is moved inside of the widget. That is, in some widgets, moving focus may also perform the select operation. However, if the widget supports multiple selection, then more than one item can be in a selected state, and keys for moving focus do not perform selection. Some multi-select widgets do support key commands that both move focus and change selection, but those keys are different from the normal navigation keys. Finally, when focus leaves a widget that includes a selected element, the selected state persists.

    From the developer's perspective, the difference is simple -- the focused element is the active element (document.activeElement). Selected elements are elements that have aria-selected="true".

    With respect to focus and the selected state, the most important considerations for designers and developers are:

    • The visual focus indicator must always be visible.

    • The selected state must be visually distinct from the focus indicator.

    hashtag
    6.4 Deciding When to Make Selection Automatically Follow Focus

    in composite widgets where only one element may be selected, such as a tablist or single-select listbox, moving the focus may also cause the focused element to become the selected element. This is called having selection follow focus. Having selection follow focus is often beneficial to users, but in some circumstances, it is extremely detrimental to accessibility.

    For example, in a tablist, the selected state is used to indicate which panel is displayed. So, when selection follows focus in a tablist, moving focus from one tab to another automatically changes which panel is displayed. If the content of panels is present in the DOM, then displaying a new panel is nearly instantaneous. A keyboard user who wishes to display the fourth of six tabs can do so with 3 quick presses of the right arrow. And, a screen reader user who perceives the labels on tabs by navigating through them may efficiently read through the complete list without any latency.

    However, if displaying a new panel causes a network request and possibly a page refresh, the effect of having selection automatically focus can be devastating to the experience for keyboard and screen reader users. In this case, displaying the fourth tab or reading through the list becomes a tedious and time-consuming task as the user experiences significant latency with each movement of focus. Further, if displaying a new tab refreshes the page, then the user not only has to wait for the new page to load but also return focus to the tab list.

    When selection does not follow focus, the user changes which element is selected by pressing the Enter or Space key.

    hashtag
    6.5 Keyboard Navigation Between Components (The Tab Sequence)

    As explained in section § 6.1 Fundamental Keyboard Navigation Conventionsarrow-up-right, all interactive UI components need to be reachable via the keyboard. This is best achieved by either including them in the tab sequence or by making them accessible from a component that is in the tab sequence, e.g., as part of a composite component. This section addresses building and managing the tab sequence, and subsequent sections cover making focusable elements that are contained within components keyboard accessible.

    The [HTMLarrow-up-right] tabindexarrow-up-right and [SVG2arrow-up-right] tabindexarrow-up-right attributes can be used to add and remove elements from the tab sequence. The value of tabindex can also influence the order of the tab sequence, although authors are strongly advised not to use tabindex for that purpose.

    In HTML, the default tab sequence of a web page includes only links and HTML form elements, except In macOS, where it includes only form elements. macOS system preferences include a keyboard setting that enables the tab key to move focus to all focusable elements.

    The default order of elements in the tab sequence is the order of elements in the DOM. The DOM order also determines screen reader reading order. It is important to keep the keyboard tab sequence and the screen reader reading order aligned, logical, and predictable as described in § 6.2 Discernible and Predictable Keyboard Focusarrow-up-right. The most robust method of manipulating the order of the tab sequence while also maintaining alignment with the reading order that is currently available in all browsers is rearranging elements in the DOM.

    The values of the tabindex attribute have the following effects.

    tabindex is not present or does not have a valid value

    The element has its default focus behavior. In HTML, only form controls and anchors with an HREF attribute are included in the tab sequence.

    tabindex="0"

    The element is included in the tab sequence based on its position in the DOM.

    tabindex="-1"

    The element is not included in the tab sequence but is focusable with element.focus().

    tabindex="X" where X is an integer in the range 1 <= X <= 32767

    Authors are strongly advised NOT to use these values. The element is placed in the tab sequence based on the value of tabindex. Elements with a tabindex value of 0 and elements that are focusable by default will be in the sequence after elements with a tabindex value of 1 or greater.

    hashtag
    6.6 Keyboard Navigation Inside Components

    As described in section § 6.1 Fundamental Keyboard Navigation Conventionsarrow-up-right, the tab sequence should include only one focusable element of a composite UI component. Once a composite contains focus, keys other than Tab and Shift + Tab enable the user to move focus among its focusable elements. Authors are free to choose which keys move focus inside of a composite, but they are strongly advised to use the same key bindings as similar components in common GUI operating systems as demonstrated in § 3. Design Patterns and Widgetsarrow-up-right.

    The convention for where focus lands in a composite when it receives focus as a result of a Tab key event depends on the type of composite. It is typically one of the following.

    • The element that had focus the last time the composite contained focus. Or, if the composite has not yet contained the focus, the first element. Widgets that usually employ this pattern include grid and tree grid.

    • The selected element. Or, if there is no selected element, the first element. Widgets where this pattern is commonly implemented include radio groups, tabs, list boxes, and trees. Note: For radio groups, this pattern is referring to the checked radio button; the selected state is not supported for radio buttons.

    • The first element. Components that typically follow this pattern include menubars and toolbars.

    The following sections explain two strategies for managing focus inside composite elements: creating a roving tabindex and using the aria-activedescendant property.

    6.6.1 Managing Focus Within Components Using a Roving tabindex

    When using roving tabindex to manage focus in a composite UI component, the element that is to be included in the tab sequence has tabindex of "0" and all other focusable elements contained in the composite have tabindex of "-1". The algorithm for the roving tabindex strategy is as follows.

    • When the component container is loaded or created, set tabindex="0" on the element that will initially be included in the tab sequence and set tabindex="-1" on all other focusable elements it contains.

    • When the component contains focus and the user presses a navigation key that moves focus within the component, such as an arrow key:

      • set tabindex="-1" on the element that has tabindex="0".

      • Set tabindex="0" on the element that will become focused as a result of the key event.

      • Set focus, element.focus(), on the element that has tabindex="0".

    • If the design calls for a specific element to be focused the next time the user moves focus into the composite with Tab or Shift+Tab, check if that target element has tabindex="0" when the composite loses focus. If it does not, set tabindex="0" on the target element and set tabindex="-1" on the element that previously had tabindex="0".

    One benefit of using roving tabindex rather than aria-activedescendant to manage focus is that the user agent will scroll the newly focused element into view.

    6.6.2 Managing Focus in Composites Using aria-activedescendant

    If a component container has an ARIA role that supports the aria-activedescendantarrow-up-right property, it is not necessary to manipulate the tabindex attribute and move DOM focus among focusable elements within the container. Instead, only the container element needs to be included in the tab sequence. When the container has DOM focus, the value of aria-activedescendant on the container tells assistive technologies which element is active within the widget. Assistive technologies will consider the element referred to as active to be the focused element even though DOM focus is on the element that has the aria-activedescendant property. And, when the value of aria-activedescendant is changed, assistive technologies will receive focus change events equivalent to those received when DOM focus actually moves.

    The steps for using the aria-activedescendant method of managing focus are as follows.

    • When the container element that has a role that supports aria-activedescendant is loaded or created, ensure that:

      • The container element is included in the tab sequence as described in § 6.5 Keyboard Navigation Between Components (The Tab Sequence)arrow-up-right or is a focusable element of a composite that implements a roving tabindexarrow-up-right.

      • It has aria-activedescendant="IDREF" where IDREF is the ID of the element within the container that should be identified as active when the widget receives focus. The referenced element needs to meet the DOM relationship requirements described below.

    • When the container element receives DOM focus, draw a visual focus indicator on the active element and ensure the active element is scrolled into view.

    • When the composite widget contains focus and the user presses a navigation key that moves focus within the widget, such as an arrow key:

      • Change the value of aria-activedescendant on the container to refer to the element that should be reported to assistive technologies as active.

      • Move the visual focus indicator and, if necessary, scrolled the active element into view.

    • If the design calls for a specific element to be focused the next time a user moves focus into the composite with Tab or Shift+Tab, check if aria-activedescendant is referring to that target element when the container loses focus. If it is not, set aria-activedescendant to refer to the target element.

    The specification for aria-activedescendantarrow-up-right places important restrictions on the DOM relationship between the focused element that has the aria-activedescendant attribute and the element referenced as active by the value of the attribute. One of the following three conditions must be met.

    1. The element referenced as active is a DOM descendant of the focused referencing element.

    2. The focused referencing element has a value specified for the aria-ownsarrow-up-right property that includes the ID of the element referenced as active.

    3. The focused referencing element has role of textboxarrow-up-right and has aria-controlsarrow-up-right property referring to an element with a role that supports aria-activedescendant and either:

      1. The element referenced as active is a descendant of the controlled element.

      2. The controlled element has a value specified for the property that includes the ID of the element referenced as active.

    hashtag
    6.7 Focusability of disabled controls

    By default, disabled HTML input elements are removed from the tab sequence. In most contexts, the normal expectation is that disabled interactive elements are not focusable. However, there are some contexts where it is common for disabled elements to be focusable, especially inside of composite widgets. For example, as demonstrated in the § 3.15 Menu or Menu bararrow-up-right pattern, disabled items are focusable when navigating through a menu with the arrow keys.

    Removing focusability from disabled elements can offer users both advantages and disadvantages. Allowing keyboard users to skip disabled elements usually reduces the number of key presses required to complete a task. However, preventing focus from moving to disabled elements can hide their presence from screen reader users who "see" by moving the focus.

    Authors are encouraged to adopt a consistent set of conventions for the focusability of disabled elements. The examples in this guide adopt the following conventions, which both reflect common practice and attempt to balance competing concerns.

    1. For elements that are in the tab sequence when enabled, remove them from the tab sequence when disabled.

    2. For the following composite widget elements, keep them focusable when disabled:

      • Options in a Listboxarrow-up-right

      • Menu items in a

      • Tab elements in a set of

      • Tree items in a

    3. For elements contained in a toolbar, make them focusable if discoverability is a concern. Here are two examples to aid with this judgment.

      1. A toolbar with buttons for moving, removing, and adding items in a list includes buttons for "Up", "Down", "Add", and "Remove". The "Up" button is disabled and its focusability is removed when the first item in the list is selected. Given the presence of the "Down" button, discoverability of the "Up" button is not a concern.

      2. A toolbar in an editor contains a set of special smart paste functions that are disabled when the clipboard is empty or when the function is not applicable to the current content of the clipboard. It could be helpful to keep the disabled buttons focusable if the ability to discover their functionality is primarily via their presence on the toolbar.

    One design technique for mitigating the impact of including disabled elements in the path of keyboard focus is employing appropriate keyboard shortcuts as described in § 6.9 Keyboard Shortcutsarrow-up-right.

    hashtag
    6.8 Key Assignment Conventions for Common Functions

    The following key assignments can be used in any context where their conventionally associated functions are appropriate. While the assignments associated with Windows and Linux platforms can be implemented and used in browsers running in macOS, replacing them with macOS assignments in browsers running on a macOS device can make the keyboard interface more discoverable and intuitive for those users. In some cases, it may also help avoid system or browser keyboard conflicts.

    Function
    Windows/Linux Key
    macOS Key

    open context menu

    Shift + F10

    Copy to clipboard

    Control + C

    Command + C

    Paste from clipboard

    Control + V

    Command + V

    hashtag
    6.9 Keyboard Shortcuts

    When effectively designed, keyboard shortcuts that focus an element, activate a widget, or both can dramatically enhance usability of frequently used features of a page or site. This section addresses some of the keyboard shortcut design and implementation factors that most impact their effectiveness, including:

    1. Understanding how keyboard shortcuts augment a keyboard interface and whether to make a particular shortcut move focus, perform a function, or both.

    2. Making key assignments and avoiding assignment conflicts with assistive technologies, browsers, and operating systems.

    3. Exposing and documenting key assignments.

    6.9.1 Designing the Scope and Behavior of Keyboard Shortcuts

    This section explains the following factors when determining which elements and features to assign keyboard shortcuts and what behavior to give each shortcut:

    1. Ensuring discovery through navigation; keyboard shortcuts enhance, not replace, standard keyboard access.

    2. Effectively choosing from among the following behaviors:

      1. Navigation: Moving focus to an element.

      2. Activation: Performing an operation associated with an element that does not have focus and might not be visible.

      3. Navigation and activation: Both moving focus to an element and activating it.

    3. Balancing efficiency and cognitive load: lack of a shortcut can reduce efficiency while too many shortcuts can increase cognitive load and clutter the experience.

    6.9.1.1 Ensure Basic Access Via Navigation

    Before assigning keyboard shortcuts, it is essential to ensure the features and functions to which shortcuts may be assigned are keyboard accessible without a keyboard shortcut. In other words, all elements that could be targets for keyboard shortcuts need to be focusable via the keyboard using the methods described in:

    • § 6.5 Keyboard Navigation Between Components (The Tab Sequence)arrow-up-right

    • § 6.6 Keyboard Navigation Inside Componentsarrow-up-right

    Do not use keyboard shortcuts as a substitute for access via navigation. This is essential to full keyboard access because:

    1. The primary means of making functions and their shortcuts discoverable is by making the target elements focusable and revealing key assignments on the element itself.

    2. If people who rely on the keyboard have to read documentation to learn which keys are required to use an interface, the interface may technically meet some accessibility standards but in practice is only accessible to the small subset of them who have the knowledge that such documentation exists, have the extra time available, and the ability to retain the necessary information.

    3. Not all devices that depend on keyboard interfaces can support keyboard shortcuts.

    6.9.1.2 Choose Appropriate Shortcut Behavior

    The following conventions may help identify the most advantageous behavior for a keyboard shortcut.

    • Move focus when the primary objective is to make navigation more efficient, e.g., reduce the number of times the user must press Tab or the arrow keys. This behavior is commonly expected when assigning a shortcut to a text box, toolbar, or composite, such as a listbox, tree, grid, or menubar. This behavior is also useful for moving focus to a section of a page, such as the main content or a complementary landmark section.

    • Activate an element without moving focus when the target context of the function is the context that contains the focus. This behavior is most common for command buttons and for functions associated with elements that are not visible, such as a "Save" option that is accessible via a menu. For example, if the focus is on an option in a listbox and a toolbar contains buttons for moving and removing options, it is most beneficial to keep focus in the listbox when the user presses a key shortcut for one of the buttons in the toolbar. This behavior can be particularly important for screen reader users because it provides confirmation of the action performed and makes performing multiple commands more efficient. For instance, when a screen reader user presses the shortcut for the "Up" button, the user will be able to hear the new position of the option in the list since it still has the focus. Similarly, when the user presses the shortcut for deleting an option, the user can hear the next option in the list and immediately decide whether to press the delete shortcut again.

    • Move focus and activate when the target of the shortcut has a single function and the context of that function is the same as the target. This behavior is typical when a shortcut is assigned to a button that opens a menu or dialog, to a checkbox, or to a navigation link or button.

    6.9.1.3 Choose Where to Add Shortcuts

    Work to draft content for this section is tracked in issue 219.arrow-up-right

    The first goal when designing a keyboard interface is simple, efficient, and intuitive operation with only basic keyboard navigation support. If basic operation of a keyboard interface is inefficient, attempting to compensate for fundamental design issues, such as suboptimal layout or command structure, by implementing keyboard shortcuts will not likely reduce user frustration. The practical implication of this is that, in most well-designed user interfaces, the percentage of functionality that needs to be accessible via a keyboard shortcut in order to create optimal usability is not very high. In many simple user interfaces, keyboard shortcuts can be entirely superfluous. And, in user interfaces with too many keyboard shortcuts, the excess shortcuts create cognitive load that make the most useful ones more difficult to remember.

    Consider the following when deciding where to assign keyboard shortcuts:

    1. To be written.

    6.9.2 Assigning Keyboard Shortcuts

    When choosing the keys to assign to a shortcut, there are many factors to consider.

    • Making the shortcut easy to learn and remember by using a mnemonic (e.g., Control + S for "Save") or following a logical or spacial pattern.

    • Localizing the interface, including for differences in which keys are available and how they behave and for language considerations that could impact mnemonics.

    • Avoiding and managing conflicts with key assignments used by an assistive technology, the browser, or the operating system.

    Methods for designing a key shortcut scheme that supports learning and memory is beyond the scope of this guide. Unless the key shortcut scheme is extensive, it is likely sufficient to mimic concepts that are familiar from common desktop software, such as browsers. Similarly, while localization is important, describing how to address it is left to other resources that specialize in that topic.

    The remainder of this section provides guidance balancing requirements and concerns related to key assignment conflicts. It is typically ideal if key assignments do not conflict with keys that are assigned to functions in the user's operating system, browser, or assistive technology. Conflicts can block efficient access to functions that are essential to the user, and a perfect storm of conflicts can trap a user. At the same time, there are some circumstances where intentional conflicts are useful. And, given the vast array of operating system, browser, and assistive technology keys, it is almost impossible to be certain conflicts do not exist. So it is also important to employ strategies that mitigate the impact of conflicts whether they are intentional or unknown.

    Note

    In the following sections, meta key refers to the Windows key on Windows-compatible keyboards and the Command key on MacOS-compatible keyboards.

    6.9.2.1 Operating System Key Conflicts

    It is essential to avoid conflicts with keys that perform system level functions, such as application and window management and display and sound control. In general, this can be achieved by refraining from the following types of assignments.

    1. Any modifier keys + any of Tab, Enter, Space, or Escape.

    2. Meta key + any other single key (there are exceptions, but they can be risky as these keys can change across versions of operating systems).

    3. Alt + a function key.

    In addition, there are some important application level features that most applications, including browsers, generally support. These include:

    1. Zoom

    2. Copy/Paste

    3. ... to be continued ...

    6.9.2.2 Assistive Technology Key Conflicts

    Even though assistive technologies have collectively taken thousands of key assignments, avoiding conflicts is relatively easy. This is because assistive technologies have had to develop key assignment schemes that avoid conflicts with both operating systems and applications. They do this by hijacking specific keys as modifiers that uniquely define their key commands. For example, many assistive technologies use the Caps Lock key as a modifier.

    Deflect assistive technology key conflicts by steering clear of the following types of assignments.

    1. Caps Lock + any other combination of keys.

    2. Insert + any combination of other keys.

    3. Scroll Lock + any combination of other keys.

    4. macOS only: Control+Option + any combination of other keys.

    6.9.2.3 Browser Key Conflicts

    While there is considerable similarity among browser keyboard schemes, the patterns within the schemes are less homogenous. Consequently, it is more difficult to avoid conflicts with browser key assignments. While the impact of conflicts is sometimes mitigated by the availability of two paths to nearly every function -- keyboard accessible menus and keyboard shortcuts, avoiding conflicts with shortcuts to heavily used functions is nonetheless important. Pay special attention to avoiding conflicts with shortcuts to:

    1. Address or location bar

    2. Notification bar

    3. Page refresh

    4. Bookmark and history functions

    5. Find functions

    6.9.2.4 Intentional Key Conflicts

    While avoiding key conflicts is usually desirable, there are circumstances where intentionally conflicting with a browser function is acceptable or even desirable. This can occur when the following combination of conditions arises:

    • A web application has a frequently used function that is similar to a browser function.

    • Users will often want to execute the web application function.

    • Users will rarely execute the browser function.

    • There is an efficient, alternative path to the browser function.

    For example, consider a save function that is available when the focus is in an editor. Most browsers use ... to be continued ...

    hashtag
    7. Grid and Table Properties

    To fully present and describe a grid or table, in addition to parsing the headers, rows, and cells using the roles described in the grid patternarrow-up-right or table patternarrow-up-right, assistive technologies need to be able to determine:

    • The number of rows and columns.

    • Whether any columns or rows are hidden, e.g., columns 1 through 3 and 5 through 8 are visible but column 4 is hidden.

    • Whether a cell spans multiple rows or columns.

    • Whether and how data is sorted.

    Browsers automatically populate their accessibility tree with the number of rows and columns in a grid or table based on the rendered DOM. However, there are many situations where the DOM does not contain the whole grid or table, such as when the data set is too large to fully render. Additionally, some of this information, like skipped columns or rows and how data is sorted, cannot be derived from the DOM structure.

    The below sections explain how to use the following properties that ARIA provides for grid and table accessibility.

    Grid and Table Property Definitions

    Property
    Definition

    aria-colcount

    Defines the total number of columns in a table, grid, or treegrid.

    aria-rowcount

    Defines the total number of rows in a table, grid, or treegrid.

    aria-colindex

    • Defines a cell's position with respect to the total number of columns within a table, grid, or treegrid.

    • Note: Numbering starts with 1, not 0.

    | | aria-rowindex |

    • Defines a cell's position with respect to the total number of rows within a table, grid, or treegrid.

    • Note: Numbering starts with 1, not 0.

    | | aria-colspan | Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid. | | aria-rowspan | Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid. | | aria-sort | Indicates if items in a row or column are sorted in ascending or descending order. |

    hashtag
    7.1 Using aria-rowcount and aria-rowindex

    When the number of rows represented by the DOM structure is not the total number of rows available for a table, grid, or treegrid, the aria-rowcount property is used to communicate the total number of rows available, and it is accompanied by the aria-rowindex property to identify the row indices of the rows that are present in the DOM.

    The aria-rowcount is specified on the element with the table, grid, or treegrid role. Its value is an integer equal to the total number of rows available, including header rows. If the total number of rows is unknown, a value of -1 may be specified. Using a value of -1 indicates that more rows are available to include in the DOM without specifying the size of the available supply.

    When aria-rowcount is used on a table, grid, or treegrid, a value for aria-rowindex property is specified on each of its descendant rows, including any header rows. The value of aria-rowindex is an integer that is:

    1. Greater than or equal to 1.

    2. Greater than the value of aria-rowindex on any previous rows.

    3. Set to the index of the first row in the span if cells span multiple rows.

    4. Less than or equal to the total number of rows.

    WARNING! Missing or inconsistent values of aria-rowindex could have devastating effects on assistive technology behavior. For example, specifying an invalid value for aria-rowindex or setting it on some but not all rows in a table, could cause screen reader table reading functions to skip rows or simply stop functioning.

    The following code demonstrates the use of aria-rowcount and aria-rowindex properties on a table containing a hypothetical class list.

    hashtag
    7.2 Using aria-colcount and aria-colindex

    When the number of columns represented by the DOM structure is not the total number of columns available for a table, grid, or treegrid, the aria-colcount property is used to communicate the total number of columns available, and it is accompanied by the aria-colindex property to identify the column indices of the columns that are present in the DOM.

    The aria-colcount is specified on the element with the table, grid, or treegrid role. Its value is an integer equal to the total number of columns available. If the total number of columns is unknown, a value of -1 may be specified. Using a value of -1 indicates that more columns are available to include in the DOM without specifying the size of the available supply.

    When aria-colcount is used on a table, grid, or treegrid, a value for aria-colindex property is either specified on each of its descendant rows or on every cell in each descendant row, depending on whether the columns are contiguous as described below. The value of aria-colindex is an integer that is:

    1. Greater than or equal to 1.

    2. When set on a cell, greater than the value set on any previous cell within the same row.

    3. Set to the index of the first column in the span if a cell spans multiple columns.

    4. Less than or equal to the total number of columns.

    WARNING! Missing or inconsistent values of aria-colindex could have devastating effects on assistive technology behavior. For example, specifying an invalid value for aria-colindex or setting it on some but not all cells in a row, could cause screen reader table reading functions to skip cells or simply stop functioning.

    7.2.1 Using aria-colindex When Column Indices Are Contiguous

    When all the cells in a row have column index numbers that are consecutive integers, aria-colindex can be set on the row element with a value equal to the index number of the first column in the set. Browsers will then compute a column number for each cell in the row.

    The following code shows a grid with 16 columns, of which columns 2 through 5 are displayed to the user. Because the set of columns is contiguous, aria-colindex can be placed on each row.

    7.2.2 Using aria-colindex When Column Indices Are Not Contiguous

    When the cells in a row have column index numbers that are not consecutive integers, aria-colindex needs to be set on each cell in the row. The following example shows a grid for an online grade book where the first two columns contain a student name and subsequent columns contain scores. In this example, the first two columns with the student name are shown, but the score columns have been scrolled to show columns 10 through 13. Columns 3 through 9 are not visible so are not in the DOM.

    hashtag
    7.3 Defining cell spans using aria-colspan and aria-rowspan

    For tables, grids, and treegrids created using elements other than HTML table elements, row and column spans are defined with the aria-rowspan and aria-colspan properties.

    The value of aria-colspan is an integer that is:

    1. Greater than or equal to 1.

    2. less than the value that would cause the cell to overlap the next cell in the same row.

    The value of aria-rowspan is an integer that is:

    1. Greater than or equal to 0.

    2. 0 means the cell spans all the remaining rows in its row group.

    3. less than the value that would cause the cell to overlap the next cell in the same column.

    The following example grid has a two row header. The first two columns have headers that span both rows of the header. The subsequent 6 columns are grouped into 3 pairs with headers in the first row that each span two columns.

    Note: When using HTML table elements, use the native semantics of the th and td elements to define row and column spans by using the rowspan and colspan attributes.

    hashtag
    7.4 Indicating sort order with aria-sort

    When rows or columns are sorted, the aria-sort property can be applied to a column or row header to indicate the sorting method. The following table describes allowed values for aria-sort.

    Description of values for aria-sort

    Value
    Description

    ascending

    Data are sorted in ascending order.

    descending

    Data are sorted in descending order.

    other

    Data are sorted by an algorithm other than ascending or descending.

    none

    Default (no sort applied).

    It is important to note that ARIA does not provide a way to indicate levels of sort for data sets that have multiple sort keys. Thus, there is limited value to applying aria-sort with a value other than none to more than one column or row.

    The following example grid uses aria-sort to indicate the rows are sorted from the highest "Quiz 2" score to the lowest "Quiz 2" score.

    hashtag
    8. Intentionally Hiding Semantics with the presentation Role

    While ARIA is primarily used to express semantics, there are some situations where hiding an element's semantics from assistive technologies is helpful. This is done with the presentationarrow-up-right role, which declares that an element is being used only for presentation and therefore does not have any accessibility semantics. The ARIA 1.1 specification also includes role nonearrow-up-right, which serves as a synonym for presentation.

    For example, consider a tabs widget built using an HTML ul element.

    Because the list is declared to be a tablist, the list items are not in a list context. It could confuse users if an assistive technology were to render those list items. Applying role presentation to the li elements tells browsers to leave those elements out of their accessibility tree. Assistive technologies will thus be unaware of the list item elements and see the tab elements as immediate children of the tablist.

    Three common uses of role presentation are:

    1. Hiding a decorative image; it is equivalent to giving the image null alt text.

    2. Suppressing table semantics of tables used for layout in circumstances where the table semantics do not convey meaningful relationships.

    3. Eliminating semantics of intervening orphan elements in the structure of a composite widget, such as a tablist, menu, or tree as demonstrated in the example above.

    hashtag
    8.1 Effects of Role presentation

    When role="presentation" is specified on an element, if a condition that requires a browser to ignore the presentation rolearrow-up-right does not exist, it has the following three effects.

    1. The element's implied ARIA role and any ARIA states and properties associated with that role are hidden from assistive technologies.

    2. Text contained by the element, i.e., inner text, as well as inner text of all its descendant elements remains visible to assistive technologies except, of course, when the text is explicitly hidden, e.g., styled with display: none or has aria-hidden="true".

    3. The roles, states, and properties of each descendant element remain visible to assistive technologies unless the descendant requires the context of the presentational element. For example:

      • If presentation is applied to a ul or ol element, each child li element inherits the presentation role because ARIA requires the listitem elements to have the parent list element. So, the li elements are not exposed to assistive technologies, but elements contained inside of those li elements, including nested lists, are visible to assistive technologies.

    hashtag
    8.2 Conditions That Cause Role presentation to be Ignored

    Browsers ignore role="presentation", and it therefore has no effect, if either of the following are true about the element to which it is applied:

    • The element is focusable, e.g. it is natively focusable like an HTML link or input, or it has a tabindex attribute.

    • The element has any of the twenty-one global ARIA states and propertiesarrow-up-right, e.g., aria-label.

    hashtag
    8.3 Example Demonstrating Effects of the presentation Role

    This code:

    when parsed by a browser, is equivalent to the following from the perspective of a screen reader or other assistive technology that relies on the browser's accessibility tree:

    hashtag
    9. Roles That Automatically Hide Semantics by Making Their Descendants Presentational

    There are some types of user interface components that, when represented in a platform accessibility API, can only contain text. For example, accessibility APIs do not have a way of representing semantic elements contained in a button. To deal with this limitation, WAI-ARIA requires browsers to automatically apply role presentation to all descendant elements of any element with a role that cannot support semantic children.

    The roles that require all children to be presentational are:

    • button

    • checkbox

    • img

    • math

    • menuitemcheckbox

    • menuitemradio

    • option

    • progressbar

    • radio

    • scrollbar

    • separator

    • slider

    • switch

    • tab

    For instance, consider the following tab element, which contains a heading.

    Because WAI-ARIA requires descendants of tab to be presentational, the following code is equivalent.

    And, from the perspective of anyone using a technology that relies on an accessibility API, such as a screen reader, the heading does not exist since the previous code is equivalent to the following.

    See the section about role presentationarrow-up-right for a detailed explanation of what it does.

    hashtag
    A. Indexes

    • Design Pattern Examples by Rolearrow-up-right

    • Design Pattern Examples by Properties and Statesarrow-up-right

    hashtag
    B. Change History

    hashtag
    B.1 Changes in July 2019 Publication of Note Release 4

    • Major additions and revisions:

      • Added section providing guidance on coding and composing accessible names and descriptions. This section comprehensively covers ARIA and HTML naming and describing techniques as well as specific guidance for every ARIA role.

      • Added example of a site navigation bar (menu system) with dropdown lists of links coded using the disclosure pattern.

      • Added an example of a date picker based on the dialog and grid patterns. A modal dialog contains a calendar grid that presents buttons for each day of a month.

      • Added an example of a date picker that uses three spin buttons for day, month, and year.

      • Image Carousel Example: Updated the example so that the rotation control is always visible instead of only being visible for keyboard users who move focus into the carousel region. Also changed the controls so they are implemented with HTML button elements instead of links with the button role.

      • Toolbar Example: Added popup text labels for icons that are displayed on both hover and focus.

    • Minor revisions of guidance or implementation were made to the following design patterns and examples:

      • Editor Menubar Example: Updated example so that meta data used by the scripts is specified using data- attributes instead of with rel attributes.

    Also see:

    • The APG 1.1 Release 4 Milestonearrow-up-right: which lists the GitHub issues that document discussions and reviews of changes included in the July 2019 publication.

    • Detailed change log with links to all commits that are new in the July 2019 publication since the January 2019 publication.arrow-up-right

    hashtag
    B.2 Changes in January 2019 Publication of Note Release 3

    • Major additions and revisions:

      • Added indexes that allow lookup of examples by role, state, or property.

      • Added a carousel design pattern and example implementation.

      • Revised radio group pattern to accommodate radio groups nested in toolbars: left and right arrow keys do not change the checked state and can move focus outside the group.

      • Redesigned toolbar example to demonstrate an editor toolbar that includes a nested radio group, toggle buttons, menu button, spin button, checkbox, and link. Also fixed several bugs.

      • Restructured the accordion example to use HTML heading elements containing HTML button elements.

      • Added a regression test framework and suite of regression tests that test all documented keyboard behaviors, roles, states, and properties for each example implementation of a pattern.

    • Minor revisions of guidance or implementation were made to the following design patterns and examples:

      • Alert dialog example: Replaced lorem ipsum placeholder with text that describes actions that trigger the alert dialog and alert message.

      • Button Example: Button activation now happens on keyup for space key and aria-hidden was removed from child svg.

    Also see:

    • The APG 1.1 Release 3 Milestonearrow-up-right: which lists the GitHub issues that document discussions and reviews of changes included in the January 2019 publication.

    • Detailed change log with links to all commits that are new in the January 2019 publication since the July 2018 publication.arrow-up-right

    hashtag
    B.3 Changes in July 2018 Publication of Note Release 2

    • Added the following:

      • Treegrid design pattern and example

      • Alert dialog example

    • Significant revisions of guidance or implementation were made to the following design patterns and examples:

      • Accordion pattern and example, including removing two optional key commands from the pattern.

      • Checkbox example bug fixes

    Also see:

    • The APG 1.1 Release 2 Milestonearrow-up-right: which lists the GitHub issues that document discussions and reviews of changes included in the July 2018 publication.

    • Detailed change log with links to all commits that are new in the July 2018 publication since the December 2017 publication of the Note.arrow-up-right

    hashtag
    B.4 Changes in December 2017 Publication as Note

    • Added the following:

      • Read Me First section

      • Combobox pattern

      • Combobox examples: 3 ARIA 1.0 style and 4 ARIA 1.1 style

      • Disclosure pattern

      • Feed example display page

      • Grid and table properties guidance section

      • Collapsible dropdown listbox example

      • Multi-thumb slider examples

      • Table pattern and example

    • The top of each example page now includes a set of four related links to:

      • Browser and Assistive Technology Support section of Read Me First

      • Report Issue page in the Github repository

    • All Javascript and CSS files used by the examples include the correct license and copyright statements.

    • Significant revisions of guidance and implementation were made to the following sections, design patterns, and examples:

      • Introduction

      • Keyboard guidance for Mac OS

    Also see:

    • 1.1 APG Release 1 milestone,arrow-up-right which lists the GitHub issues that document discussions and reviews of changes included in the December 2017 publication.

    • Detailed change log with links to all commits that are new in the December 2017 publication as a Note since the June 2017 working draft.arrow-up-right

    hashtag
    B.5 Changes in June 2017 Working Draft

    • Added the following:

      • Modal dialog example

      • Disclosure design pattern

      • Example disclosure for FAQ

      • Example disclosure for image description

      • Draft of feed pattern

      • Example feed implementation

      • Example of menu button using aria-activedescendant

      • Example of tabs with manual activation

    • Design pattern section: moved examples subsection of each design pattern to be the first subsection.

    • Across all example pages:

      • Improved visual design of tables that document keyboard implementation, roles, states, and properties.

      • Improved consistency of editorial style.

    • Significant revisions of guidance and implementation were made to the following design patterns and example pages:

      • Accordion example

      • Alert example

    Also see:

    • January 2017 Clean Up Milestonearrow-up-right, which lists the GitHub issues that document discussions and reviews of changes included in the June 2017 working draft.

    • Detailed change log with links to all commits that are new in the June 2017 working draft.arrow-up-right

    hashtag
    C. Acknowledgements

    hashtag
    C.1 Major Contributors to Version 1.1

    While WAI-ARIA Authoring Practices 1.1 is the work of the entire Authoring Practices Task Force and also benefits from many people throughout the open source community who both contribute significant work and provide valuable feedback, special thanks goes to the following people who provided distinctly large portions of the content and code in version 1.1.

    • Jon Gunderson and Nicholas Hoyt of the Division of Disability Resources and Education Services at the University of Illinois Urbana/Champaign and the students Max Foltz, Sulaiman Sanaullah, Mark McCarthy, and Jinyuan Zhou for their contributions to the development of many of the design pattern examples.

    • Valerie Young of Bocoup and her sponsor, Facebook, for development of the example test framework and regressions tests for more than 50 examples.

    • Simon Pieters of Bocoup and his sponsor, Facebook, for authoring of significant guidance sections, including comprehensive treatement of the topic of accessible names and descriptions.

    hashtag
    C.2 Participants active in the ARIA Authoring Practices Task Force

    • Ann Abbott (Invited Expert)

    • Shirisha Balusani (Microsoft Corporation)

    • Dorothy Bass (Wells Fargo Bank N.A.)

    • Curt Bellew (Oracle)

    • Zoë Bijl (Invited Expert)

    • Michael Cooper (W3C)

    • Bryan Garaventa (Level Access)

    • Jon Gunderson (University of Illinois at Urbana-Champaign)

    • Jesse Hausler(Salesforce)

    • Sarah Higley (Microsoft Corporation)

    • Hans Hillen (The Paciello Group, LLC)

    • Matt King (Facebook)

    • Jaeun Ku (University of Illinois at Urbana-Champaign)

    • Aaron Leventhal (Google)

    • Carolyn MacLeod (IBM Corporation)

    • Mark McCarthy (University of Illinois at Urbana-Champaign)

    • James Nurthen (Adobe)

    • Scott O'Hara (The Paciello Group, LLC)

    • Simon Pieters (Bocoup)

    • Scott Vinkle (Shopify)

    • Evan Yamanishi (W. W. Norton)

    • Valerie Young (Bocoup)

    hashtag
    C.3 Other commenters and contributors to Version 1.1

    • Vyacheslav Aristov

    • J. Renée Beach

    • Kasper Christensen

    • Gerard K. Cohen

    • Anne-Gaelle Colom

    • Kevin Coughlin

    • Cameron Cundiff

    • Manish Dahamiwal

    • Gilmore Davidson

    • Boris Dušek

    • Michael Fairchild

    • Jeremy Felt

    • Rob Fentress

    • Geppy

    • Tatiana Iskandar

    • Patrick Lauke

    • Marek Lewandowski

    • Dan Matthew

    • Shane McCarron

    • Victor Meyer

    • Jonathan Neal

    • Philipp Rudloff

    • Joseph Scheuhammer

    • Nick Schonning

    • thomascorthals

    • Christopher Tryens

    hashtag
    C.4 Enabling funders

    This publication has been funded in part with U.S. Federal funds from the Department of Education, National Institute on Disability, Independent Living, and Rehabilitation Research (NIDILRR), initially under contract number ED-OSE-10-C-0067 and currently under contract number HHSP23301500054C. The content of this publication does not necessarily reflect the views or policies of the U.S. Department of Education, nor does mention of trade names, commercial products, or organizations imply endorsement by the U.S. Government.

    hashtag
    D. References

    hashtag
    D.1 Informative references

    [HTML]

    HTML Standardarrow-up-right. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/arrow-up-right

    [HTML-AAM]

    HTML Accessibility API Mappings 1.0arrow-up-right. Steve Faulkner; Alexander Surkov; Scott O'Hara; Bogdan Brinza; Jason Kiss; Cynthia Shelly. W3C. 10 July 2019. W3C Working Draft. URL: https://www.w3.org/TR/html-aam-1.0/arrow-up-right

    [HTML-ARIA]

    ARIA in HTMLarrow-up-right. Steve Faulkner. W3C. 5 July 2019. W3C Working Draft. URL: https://www.w3.org/TR/html-aria/arrow-up-right

    [SVG2]

    Scalable Vector Graphics (SVG) 2arrow-up-right. Amelia Bellamy-Royds; Bogdan Brinza; Chris Lilley; Dirk Schulze; David Storey; Eric Willigers. W3C. 4 October 2018. W3C Candidate Recommendation. URL: https://www.w3.org/TR/SVG2/arrow-up-right

    [WAI-ARIA]

    Accessible Rich Internet Applications (WAI-ARIA) 1.1arrow-up-right. Joanmarie Diggs; Shane McCarron; Michael Cooper; Richard Schwerdtfeger; James Craig. W3C. 14 December 2017. W3C Recommendation. URL: https://www.w3.org/TR/wai-aria-1.1/arrow-up-right

    This approach makes it easy to identify and manage the CSS that belongs to a specific component. However, it also fragments your stylesheet across your codebase, and this fragmentation might not be worthwhile. For larger applications with hundreds of unique views and lots of moving parts, it makes sense to limit the amount of irrelevant code that's sent to your user. You'll likely have app-wide styles and specific component styles that built on top of those.

    You can read more about component stylesheets in the create-react-app docsarrow-up-right.

    We used console.log() to check on the state and props of our application in this tutorial, and you'll also have seen some of the useful warnings and error message that react gives you both in the CLI and your browser's JavaScript console. But there's more we can do here.

    The React DevTools utility allows you to inspect the internals of your React application directly in the browser. It adds a new panel to your browser's developer tools, and with it you can inspect the state and props of various components, and even edit state and props to make immediate changes to your application.

    This screenshot shows our finished application as it appears in React DevTools:

    Our project being shown in React devtools

    On the left, we see all of the components that make up our application, including some unique keys for the things that are rendered from arrays. On the right, we see the props and hooks that our App component utilizes. Notice, too, that the Form, FilterButton, and Todo components are indented to the right – this indicates that App is their parent. In more complex apps, this view is great for understanding parent/child relationships at a glance.

    React DevTools is available in a number of forms:

    • A Chrome browser extensionarrow-up-right.

    • A Firefox browser extensionarrow-up-right.

    • A Chromium Edge browser extension (available soon).

    • A .

    Try installing one of these, then using it to inspect the app you've just built!

    You can read more about React DevTools on the React blogarrow-up-right.

    hashtag
    The Context APIarrow-up-right

    The application that we built in this tutorial utilized component props to pass data from its App component to the child components that needed it. Most of the time, props are an appropriate method for sharing data; for complex, deeply nested applications, however, they're not always best.

    React provides the Context APIarrow-up-right as a way to provide data to components that need it without passing props down the component tree. There's also a useContext hookarrow-up-right that facilitates this.

    If you'd like to try this API for yourself, Smashing Magazine has written an introductory article about React contextarrow-up-right.

    hashtag
    Class componentsarrow-up-right

    Although this tutorial doesn't mention them, it is possible to build React components using ES6 classes – these are called class components. Until the arrival of hooks, ES6 classes were the only way to bring state into components or manage rendering side effects. They're still the only way to handle certain other, more edge-case features, and they're very common in legacy React projects. The official React docs are a great place to start learning about them.

    • State and Lifecycle in the React Docsarrow-up-right

    • Intro To React in the React Docsarrow-up-right

    • Read about JavaScript classes at MDNarrow-up-right

    hashtag
    Testingarrow-up-right

    create-react-app provides some tools for testing your application out of the box — you may have deleted the relevant files earlier in the tutorial. The documentation for create-react-app covers some basics for testingarrow-up-right.

    hashtag
    Routingarrow-up-right

    While routing is traditionally handled by a server and not an application on the user's computer, it is possible to configure a web application to read and update the browser's location, and render certain user interfaces. This is called client-side routing. It's possible to create many unique routes for your application (such as /home, /dashboard, or login/).

    The React community has produced two major libraries for client-side routing: React Routerarrow-up-right and Reach Routerarrow-up-right.

    • React Router is well-suited to applications with complex routing needs, and it meets some edge cases better than Reach Router. React Router is a larger library, however.

    • Reach Router is well-suited to simpler applications, and automatically manages focus as the user navigates from page to page.

    Focus management is essential in client-side routing — without it, keyboard users can be trapped in focus limbo, and screen-reader users may have no idea that they have moved to a new page. Because Reach Router is better for accessibility, it's a good place to start.

    There's one caveat, however: these projects will be merging in the near futurearrow-up-right. When this merge happens, React Router will be the surviving project (with the addition of the focus management features of Reach).

    Component-level stylesarrow-up-right
    . It'll be similar to
    deleteTask()
    because it'll take an
    id
    to find its target object, but it'll also take a
    newName
    property containing the name to update the task to. We'll use
    instead of
    because we want to return a new array with some changes, instead of deleting something from the array.

    Add the editTask() function inside your App component, in the same place as the other functions:

    Pass editTask into our <Todo /> components as a prop in the same way we did with deleteTask:

    Now open Todo.js. We're going to do some refactoring.

    hashtag
    A UI for editingarrow-up-right

    In order to allow users to edit a task, we have to provide a user interface for them to do so. First, import useState into the Todo component like we did before with the App component, by updating the first import statement to this:

    We'll now use this to set an isEditing state, the default state of which should be false. Add the following line just inside the top of your Todo(props) { … } component definition:

    Next, we're going to rethink the <Todo /> component — from now on, we want it to display one of two possible "templates", rather than the single template it's used so far:

    • The "view" template, when we are just viewing a todo; this is what we've used in the tutorial thus far.

    • The "editing" template, when we are editing a todo. We're about to create this.

    Copy this block of code into the Todo() function, beneath your useState() hook but above the return statement:

    We've now got the two different template structures — "edit" and "view" — defined inside two separate constants. This means that the return statement of <Todo /> is now repetitious — it also contains a definition of the "view" template. We can clean this up by using conditional rendering to determine which template the component returns, and is therefore rendered in the UI.

    hashtag
    Conditional renderingarrow-up-right

    In JSX, we can use a condition to change what is rendered by the browser. To write a condition in JSX, we can use a ternary operatorarrow-up-right.

    In the case of our <Todo /> component, our condition is "Is this task being edited?" Change the return statement inside Todo() so that it reads like so:

    Your browser should render all your tasks just like before. To see the editing template, you will have to change the default isEditing state from false to true in your code for now; we will look at making the edit button toggle this in the next section!

    hashtag
    Toggling the <Todo /> templatesarrow-up-right

    At long last, we are ready to make our final core feature interactive. To start with, we want to call setEditing() with a value of true when a user presses the "Edit" button in our viewTemplate, so that we can switch templates.

    Update the "Edit" button in the viewTemplate like so:

    Now we'll add the same onClick handler to the "Cancel" button in the editingTemplate, but this time we'll set isEditing to false so that it switches us back to the view template.

    Update the "Cancel" button in the editingTemplate like so:

    With this code in place, you should be able to press the "Edit" and "Cancel" buttons in your todo items to toggle between templates.

    The eat todo item showing the view template, with edit and delete buttons available
    The eat todo item showing the edit template, with an input field to enter a new name, and cancel and save buttons available

    The next step is to actually make the editing functionality work.

    hashtag
    Editing from the UIarrow-up-right

    Much of what we're about to do will mirror the work we did in Form.js: as the user types in our new input field, we need to track the text they enter; once they submit the form, we need to use a callback prop to update our state with the new name of the task.

    We'll start by making a new hook for storing and setting the new name. Still in Todo.js, put the following underneath the existing hook:

    Next, create a handleChange() function that will set the new name; put this underneath the hooks but before the templates:

    Now we'll update our editingTemplate's <input /> field, setting a value attribute of newName, and binding our handleChange() function to its onChange event. Update it as follows:

    Finally, we need to create a function to handle the edit form's onSubmit event; add the following just below the previous function you added:

    Remember that our editTask() callback prop needs the ID of the task we're editing as well as its new name.

    Bind this function to the form's submit event by adding the following onSubmit handler to the editingTemplate's <form>:

    You should now be able to edit a task in your browser!

    hashtag
    Back to the filter buttonsarrow-up-right

    Now that our main features are complete, we can think about our filter buttons. Currently, they repeat the "All" label, and they have no functionality! We will be reapplying some skills we used in our <Todo /> component to:

    • Create a hook for storing the active filter.

    • Render an array of <FilterButton /> elements that allow users to change the active filter between all, completed, and incomplete.

    hashtag
    Adding a filter hookarrow-up-right

    Add a new hook to your App() function that reads and sets a filter. We want the default filter to be All because all of our tasks should be shown initially:

    hashtag
    Defining our filtersarrow-up-right

    Our goal right now is two-fold:

    • Each filter should have a unique name.

    • Each filter should have a unique behavior.

    A JavaScript object would be a great way to relate names to behaviors: each key is the name of a filter; each property is the behavior associated with that name.

    At the top of App.js, beneath our imports but above our App() function, let's add an object called FILTER_MAP:

    The values of FILTER_MAP are functions that we will use to filter the tasks data array:

    • The All filter shows all tasks, so we return true for all tasks.

    • The Active filter shows tasks whose completed prop is false.

    • The Completed filter shows tasks whose completed prop is true.

    Beneath our previous addition, add the following — here we are using the Object.keys()arrow-up-right method to collect an array of FILTER_NAMES:

    Note: We are defining these constants outside our App() function because if they were defined inside it, they would be recalculated every time the <App /> component re-renders, and we don't want that. This information will never change no matter what our application does.

    hashtag
    Rendering the filtersarrow-up-right

    Now that we have the FILTER_NAMES array, we can use it to render all three of our filters. Inside the App() function we can create a constant called filterList, which we will use to map over our array of names and return a <FilterButton /> component. Remember, we need keys here, too.

    Add the following underneath your taskList constant declaration:

    Now we'll replace the three repeated <FilterButton />s in App.js with this filterList. Replace the following:

    With this:

    This won't work yet. We've got a bit more work to do first.

    hashtag
    Interactive filtersarrow-up-right

    To make our filter buttons interactive, we should consider what props they need to utilize.

    • We know that the <FilterButton /> should report whether it is currently pressed, and it should be pressed if its name matches the current value of our filter state.

    • We know that the <FilterButton /> needs a callback to set the active filter. We can make direct use of our setFilter hook.

    Update your filterList constant as follows:

    In the same way as we did earlier with our <Todo /> component, we now have to update FilterButton.js to utilize the props we have given it. Do each of the following, and remember to use curly braces to read these variables!

    • Replace all with {props.name}.

    • Set the value of aria-pressed to {props.isPressed}.

    • Add an onClick handler that calls props.setFilter() with the filter's name.

    With all of that done, your FilterButton() function should read like this:

    Visit your browser again. You should see that the different buttons have been given their respective names. When you press a filter button, you should see its text take on a new outline — this tells you it has been selected. And if you look at your DevTool's Page Inspector while clicking the buttons, you'll see the aria-pressed attribute values change accordingly.

    The three filter buttons of the app - all, active, and completed - with a focus highlight around completed

    However, our buttons still don't actually filter the todos in the UI! Let's finish this off.

    hashtag
    Filtering tasks in the UIarrow-up-right

    Right now, our taskList constant in App() maps over the tasks state and returns a new <Todo /> component for all of them. This is not what we want! A task should only render if it is included in the results of applying the selected filter. Before we map over the tasks state, we should filter it (with Array.prototype.filter()arrow-up-right) to eliminate objects we don't want to render.

    Update your taskList like so:

    In order to decide which callback function to use in Array.prototype.filter(), we access the value in FILTER_MAP that corresponds to the key of our filter state. When filter is All, for example, FILTER_MAP[filter] will evaluate to () => true.

    Choosing a filter in your browser will now remove the tasks that do not meet its criteria. The count in the heading above the list will also change to reflect the list!

    The app with the filter buttons in place. Active is highlighted, so only the active todo items are being shown.

    hashtag
    Summaryarrow-up-right

    So that's it — our app is now functionally complete. However, now that we've implemented all of our features, we can make a few improvements to ensure that a wider range of users can use our app. Our next article rounds things off for our React tutorials by looking at including focus management in React, which can improve usability and reduce confusion for both keyboard-only and screenreader users.

    Editing the name of a taskarrow-up-right
    Array.prototype.map()arrow-up-right
    Array.prototype.filter()arrow-up-right
    accessibility supportedarrow-up-right

    Conditional Rendering

    In React, you can create distinct components that encapsulate behavior you need. Then, you can render only some of them, depending on the state of your application.

    Conditional rendering in React works the same way conditions work in JavaScript. Use JavaScript operators like or the to create elements representing the current state, and let React update the UI to match them.

    Consider these two components:

    We'll create a Greeting component that displays either of these components depending on whether a user is logged in:

    Design Principles

    We wrote this document so that you have a better idea of how we decide what React does and what React doesn't do, and what our development philosophy is like. While we are excited to see community contributions, we are not likely to choose a path that violates one or more of these principles.

    Note:

    This document assumes a strong understanding of React. It describes the design principles of React itself, not React components or applications.

    For an introduction to React, check out Thinking in React instead.

    Forms

    HTML form elements work a bit differently from other DOM elements in React, because form elements naturally keep some internal state. For example, this form in plain HTML accepts a single name:

    This form has the default HTML form behavior of browsing to a new page when the user submits the form. If you want this behavior in React, it just works. But in most cases, it's convenient to have a JavaScript function that handles the submission of the form and has access to the data that the user entered into the form. The standard way to achieve this is with a technique called "controlled components".

    hashtag
    Controlled Components

    Legacy Context

    Note:

    The legacy context API will be removed in a future major version. Use the new context API introduced with version 16.3. The legacy API will continue working for all 16.x releases.

    hashtag
    How To Use Context

    Hooks API Reference

    Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

    This page describes the APIs for the built-in Hooks in React.

    If you're new to Hooks, you might want to check out the overview first. You may also find useful information in the frequently asked questions section.

    import React, {
        useState
    } from 'react'
    import {
        render,
        fireEvent
    } from '@testing-library/react'
    function CostInput() {
    
    const [ value, setValue ] = useState( '' ) removeDollarSign = value => ( value[ 0 ] === '$' ? value.slice( 1 ) : value ) getReturnValue = value => ( value === '' ? '' : `$${value}` ) handleChange = ev => {
            ev.preventDefault()
    const inputtedValue = ev.currentTarget.value
    
    const noDollarSign = removeDollarSign( inputtedValue ) if ( isNaN( noDollarSign ) ) return setValue( getReturnValue( noDollarSign ) )
        }
        return <input value = {
            value
        }
        aria - label = "cost-input"
        onChange = {
            handleChange
        }
        />}
    const setup = () => {
    const utils = render(<CostInput / > )
    const input = utils.getByLabelText( 'cost-input' ) return {
        input,
        ...utils,
    }
    }
    test( 'It should keep a $ in front of the input', () => {
    
    const {
                input
            } = setup() fireEvent.change( input, {
                target: {
                    value: '23'
                }
            } ) expect( input.value ).toBe( '$23' )
        } ) test( 'It should allow a $ to be in the input when the value is changed', () => {
    
    const {
                input
            } = setup() fireEvent.change( input, {
                target: {
                    value: '$23.0'
                }
            } ) expect( input.value ).toBe( '$23.0' )
        } ) test( 'It should not allow letters to be inputted', () => {
    
    const {
                    input
                } = setup() expect( input.value ).toBe( '' )
    // empty before  fireEvent.change(input, {target: {value: 'Good Day'}})  expect(input.value).toBe('')
    //empty after})test('It should allow the $ to be deleted', () => {
    const {input} = setup()  fireEvent.change(input, {target: {value: '23'}})  expect(input.value).toBe('$23')
    // need to make a change so React registers "" as a change  fireEvent.change(input, {target: {value: ''}})  expect(input.value).toBe('')})
    npm run storybook
    export const NoIcons = Template.bind();
    NoIcons.args = {
      items: itemsOptions["3"],
    };
    export const Icons = Template.bind({});
    Icons.args = {
      items: itemsOptions["5"],
    };
    export default {
      title: "Components/Accordion",
      component: AccordionComponent,
      argTypes: {
        theme: {
          name: "Theme",
        },
        closeOthers: {
          name: "One Panel Open At A Time",
        },
      },
    };
    
    const Template = (args) => <AccordionComponent {...args} />;
    
    export const Primary = Template.bind({});
    
    Primary.args = {
      ...props,
    };
    argTypes: {
      backgroundColor: {
        control: {
          type: 'radio',
          options: ['white', 'gray'],
        },
        defaultValue: 'white',
      },
    },
    argTypes: {
      items: {
        name: 'How many items?',
        control: {
          type: 'select',
          options: {
            ...itemsPrimary,
          },
        },
      },
    },
    argTypes: {
      title: {
        control: {
          type: 'text',
        },
      },
      subtitle: {
        control: {
          type: 'text',
        },
      },
      link: {
        control: 'boolean',
      },
      ctaType: {
        control: {
          type: 'inline-radio',
          options: ['ImageSlide', 'VideoSlide'],
        },
      },
    },
    import React from "react";
    import MyComponent from "./index";
    import { Data } from "./data";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    
    const props = MyComponentComposition({ fields: Data });
    
    export default {
      title: "Components/MyComponent",
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: "radio",
            options: ["white", "gray"],
          },
          defaultValue: "white",
        },
      },
    };
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    import React from 'react';
    import MyComponent from './index';
    import { Data } from './data';
    
    ...
    ...
    
    import { MyComponent as MyComponentComposition } from '../../lib/composition';
    
    
    const props = MyComponentComposition({ fields: Data });
    
    ...
    ...
    
    export default {
      title: 'Components/MyComponent',
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: 'radio',
            options: ['white', 'gray'],
          },
          defaultValue: 'white',
        },
      },
    };
    
    ...
    ...
    
    
    const Template = args => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    ...
    
    import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';
    
    
    const props = BulletedOverviewComposition(Data);
    
    const itemsToShow = createStoryOptions(props.items);
    
    export default {
      title: 'Components/Bulleted Overview',
      component: BulletedOverview,
      argTypes: {
        ...storybookCustomArgs(props, BulletedOverview, [], false),
        items: {
          name: 'How many items?',
          control: {
            type: 'select',
            options: {
              ...itemsToShow,
            },
          },
        },
      },
    };
    
    ...
    import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)test('should show login form', () => {  render(<Login />)  const input = screen.getByLabelText('Username')  // Events and assertions...})
    <body>  <div id="app">    <label for="username-input">Username</label>    <input id="username-input" />  </div></body>
    import {screen, getByLabelText} from '@testing-library/dom'// With screen:const inputNode1 = screen.getByLabelText('Username')// Without screen, you need to provide a container:const container = document.querySelector('#app')const inputNode2 = getByLabelText(container, 'Username')
    import {screen} from '@testing-library/dom'document.body.innerHTML = `  <label for="example">Example</label>  <input id="example" />`const exampleInput = screen.getByLabelText('Example')
    // Matching a string:screen.getByText('Hello World') // full string matchscreen.getByText('llo Worl', {exact: false}) // substring matchscreen.getByText('hello world', {exact: false}) // ignore case// Matching a regex:screen.getByText(/World/) // substring matchscreen.getByText(/world/i) // substring match, ignore casescreen.getByText(/^hello world$/i) // full string match, ignore casescreen.getByText(/Hello W?oRlD/i) // substring match, ignore case, searches for "hello world" or "hello orld"// Matching with a custom function:screen.getByText((content, element) => content.startsWith('Hello'))
    // full string does not matchscreen.getByText('Goodbye World')// case-sensitive regex with different casescreen.getByText(/hello world/)// function looking for a span when it's actually a div:screen.getByText((content, element) => {  return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')})
    screen.getByText('text', {  normalizer: getDefaultNormalizer({trim: false}),})
    screen.getByText('text', {  normalizer: str =>    getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''),})
    import {screen} from '@testing-library/dom'document.body.innerHTML = `  <button>test</button>  <span>multi-test</span>  <div>multi-test</div>`// debug documentscreen.debug()// debug single elementscreen.debug(screen.getByText('test'))// debug multiple elementsscreen.debug(screen.getAllByText('multi-test'))
    import {screen} from '@testing-library/dom'document.body.innerHTML = `  <button>test</button>  <span>multi-test</span>  <div>multi-test</div>`// log entire document to testing-playgroundscreen.logTestingPlaygroundURL()// log a single elementscreen.logTestingPlaygroundURL(screen.getByText('test'))
    // @testing-library/reactconst {container} = render(<MyComponent />)const foo = container.querySelector('[data-foo="bar"]')
            <div role="button">Place Order</div>
            <a role="menuitem">Assistive tech users perceive this element as an item in a menu, not a link.</a>
            <a aria-label="Assistive tech users can only perceive the contents of this aria-label, not the link text">Link Text</a>
            <button aria-pressed="false">Mute</button>
      <table role="log">
        <!--
          Table that assistive technology users will not perceive as a table.
          The log role tells browser this is a log, not a table.
        -->
      </table>
      <ul role="navigation">
        <!-- This is a navigation region, not a list. -->
        <li><a href="uri1">nav link 1</li>
        <li><a href="uri2">nav link 2</li>
        <!-- ERROR! Previous list items are not in a list! -->
      </ul>
    <a href="/">Home</a>
    <ul role="tree">
      <li role="treeitem">Fruits
        <ul role="group">
          <li role="treeitem">Apples</li>
          <li role="treeitem">Bananas</li>
          <li role="treeitem">Oranges</li>
        </ul>
      </li>
    </ul>
    <button type="button" aria-label="Close">X</button>
    <nav aria-label="Product">
      <!-- list of navigation links to product pages -->
    </nav>
    <span id="night-mode-label">Night mode</span>
    <span role="switch" aria-checked="false" tabindex="0" aria-labelledby="night-mode-label"></span>
    <label for="night-mode">Night mode</label>
    <input type="checkbox" role="switch" id="night-mode">
    <span id="night-mode-label" hidden>Night mode</span>
    <input type="checkbox" role="switch" aria-labelledby="night-mode-label">
    <h2 id="bees-heading">7 ways you can help save the bees</h2>
    <p>Bees are disappearing rapidly. Here are seven things you can do to help.</p>
    <p><a id="bees-read-more" aria-labelledby="bees-read-more bees-heading">Read more...</a></p>
    <button id="download-button" aria-labelledby="download-button download-details">Download</button>
    <span id="download-details">PDF, 2.4 MB</span>
    <label>
      <input type="checkbox" name="subscribe">
      subscribe to our newsletter
    </label>
    <input type="checkbox" name="subscribe" id="subscribe_checkbox">
    <label for="subscribe_checkbox">subscribe to our newsletter</label>
    <table>
     <caption>Special opening hours</caption>
     <tr><td>30 May <td>Closed
     <tr><td>6 June <td>11:00-16:00
    </table>
    <table>
     <caption>Table 1. Traditional dietary intake of Okinawans and other Japanese circa 1950</caption>
     <thead>
      <tr>
       <th>
       <th>Okinawa, 1949
       <th>Japan, 1950
     <tbody>
      <tr>
       <th>Total calories
       <td>1785
       <td>2068
    
      [...]
    
    </table>
    <figure>
     <img alt="Painting of a person walking in a desert." src="Hole_JesusalDesierto.jpg">
     <figcaption>Jesus entering the desert as imagined by William Hole, 1908</figcaption>
    </figure>
    <fieldset title="Select your starter class">
      <label><input type="radio" name="starter-class" value="green"> Green</label>
      <label><input type="radio" name="starter-class" value="red"> Red</label>
      <label><input type="radio" name="starter-class" value="blue"> Blue</label>
    </fieldset>
    <!-- Using a <label> is recommended -->
    <label>Search <input type=search name=q></label>
    
    <!-- A placeholder is used as fallback -->
    <input type=search name=q placeholder="Search">
    <ul role="tree">
      <li role="treeitem">Fruits
        <ul role="group">
          <li role="treeitem">Apples</li>
          <li role="treeitem">Bananas</li>
          <li role="treeitem">Oranges</li>
        </ul>
      </li>
    </ul>
    <ul role="menu">
      <li role="menuitem">Fruits
        <ul role="menu">
          <li role="menuitem">Apples</li>
          <li role="menuitem">Bananas</li>
          <li role="menuitem">Oranges</li>
        </ul>
      </li>
    </ul>
    <input name="code">
    <input name="code"
           placeholder="One-time code">
    <input name="code"
           placeholder="123456"
           title="One-time code">
    <label>One-time code
     <input name="code"
            placeholder="123456"
            title="Get your code from the app.">
    </label>
    <label>Code
     <input name="code"
            aria-label="One-time code"
            placeholder="123456"
            title="Get your code from the app.">
    </label>
    <p>Please fill in your <span id="code-label">one-time code</span> to log in.</p>
    <p>
     <label>Code
      <input name="code"
             aria-labelledby="code-label"
             aria-label="This is ignored"
             placeholder="123456"
             title="Get your code from the app.">
     </label>
    </p>
    <button>Move to <img src="bin.svg" alt="trash"></button>
    <div id="meeting-1">
      <button aria-labelledby="meeting-1" aria-label="Remove meeting:">X</button>
      Daily status report
    </div>
    <button aria-describedby="trash-desc">Move to trash</button>
    ...
    <p id="trash-desc">Items in the trash will be permanently removed after 30 days.</p>
    <button aria-describedby="trash-desc"> Move to <img src="bin.svg" alt="trash"></button>
    ...
    <p id="trash-desc">Items in <img src="bin.svg" alt="the trash"> will be permanently removed after 30 days.</p>
    <label for="username">Username</label>
    <input id="username" name="username" aria-describedby="username-desc">
    <button aria-expanded="false" aria-controls="username-desc" aria-label="Help about username">?</button>
    <p id="username-desc" hidden>
      Your username is the name that you use to log in to this service.
    </p>
    <h2 id="events-heading">Upcoming events</h2>
    <table aria-labelledby="events-heading">
     <caption>
      Calendar of upcoming events, weeks 27 through 31, with each week starting with
      Monday. The first column is the week number.
     </caption>
     <tr><th>Week<th>Monday<th>Tuesday<th>Wednesday<th>Thursday<th>Friday<th>Saturday<th>Sunday
     <tr><td>27<td><td><td><td><td><td><td>
     <tr><td>28<td><td><td><td><td><td><td><a href="/events/9856">Crown Princess's birthday</a>
     <tr><td>29<td><td><td><td><td><td><td>
     <tr><td>30<td><td><td><td><td><td><td>
     <tr><td>31<td><td><td><td><td><td><td>
    </table>
    <h2 id="neutron">Neutron</h2>
    <figure aria-labelledby="neutron" aria-describedby="neutron-caption">
     <img src="neutron.svg" alt="Within the neutron are three quarks (blue 'u',
     red 'd', green 'd') that are interconnected.">
     <figcaption id="neutron-caption">
      The quark content of the neutron. The color assignment of individual quarks is
      arbitrary, but all three colors must be present. Forces between quarks are
      mediated by gluons.
     </figcaption>
    </figure>
    <label> Part number:
     <input pattern="[0-9][A-Z]{3}" name="part"
            title="A part number is a digit followed by three uppercase letters."/>
    </label>
    <a href="http://twitter.com/W3C"
       title="Follow W3C on Twitter">
       <img src="/2008/site/images/Twitter_bird_logo_2012.svg"
            alt="Twitter" class="social-icon" height="40" />
    </a>
      <!--
        aria-rowcount tells assistive technologies the actual size of the grid
        is 463 rows even though only 4 rows are present in the markup.
      -->
      <table role="grid" aria-rowcount="463">
        aria-label="Student roster for history 101"
        <thead>
          <tr aria-rowindex="1">
            <th>Last Name</th>
            <th>First Name</th>
            <th>E-mail</th>
            <th>Major</th>
            <th>Minor</th>
            <th>Standing</th>
          </tr>
        </thead>
        <tbody>
            <!--
              aria-rowindex tells assistive technologies that this
              row is row 51 in the grid of 463 rows.
            -->
          <tr aria-rowindex="51">
            <td>Henderson</td>
            <td>Alan</td>
            <td>ahederson56@myuniveristy.edu</td>
            <td>Business</td>
            <td>Spanish</td>
            <td>Junior</td>
          </tr>
            <!--
              aria-rowindex tells assistive technologies that this
              row is row 52 in the grid of 463 rows.
            -->
          <tr aria-rowindex="52">
            <td>Henderson</td>
            <td>Alice</td>
            <td>ahederson345@myuniveristy.edu</td>
            <td>Engineering</td>
            <td>none</td>
            <td>Sophomore</td>
          </tr>
            <!--
              aria-rowindex tells assistive technologies that this
              row is row 53 in the grid of 463 rows.
            -->
          <tr aria-rowindex="53">
            <td>Henderson</td>
            <td>Andrew</td>
            <td>ahederson75@myuniveristy.edu</td>
            <td>General Studies</td>
            <td>none</td>
            <td>Freshman</td>
          </tr>
        </tbody>
      </table>
    <div role="grid" aria-colcount="16">
      <div role="rowgroup">
        <div role="row" aria-colindex="2">
          <span role="columnheader">First Name</span>
          <span role="columnheader">Last Name</span>
          <span role="columnheader">Company</span>
          <span role="columnheader">Address</span>
        </div>
      </div>
      <div role="rowgroup">
        <div role="row" aria-colindex="2">
          <span role="gridcell">Fred</span>
          <span role="gridcell">Jackson</span>
          <span role="gridcell">Acme, Inc.</span>
          <span role="gridcell">123 Broad St.</span>
        </div>
        <div role="row" aria-colindex="2">
          <span role="gridcell">Sara</span>
          <span role="gridcell">James</span>
          <span role="gridcell">Acme, Inc.</span>
          <span role="gridcell">123 Broad St.</span>
        </div>
       …
      </div>
    </div>
      <table role="grid" aria-rowcount="463" aria-colcount="13">
        aria-label="Student grades for history 101"
        <!--
          aria-rowcount and aria-colcount tell assistive technologies
          the actual size of the grid is 463 rows by 13 columns,
          which is not the number rows and columns found in the markup.
        -->
        <thead>
          <tr aria-rowindex="1">
            <!--
              aria-colindex tells assistive technologies that the
              following columns represent columns 1 and 2 of the total data set.
            -->
            <th aria-colindex="1">Last Name</th>
            <th aria-colindex="2">First Name</th>
            <!--
              aria-colindex tells users of assistive technologies that the
              following columns represent columns 10, 11, 12, and 13 of
              the overall data set of grades.
            -->
            <th aria-colindex="10">Homework 4</th>
            <th aria-colindex="11">Quiz 2</th>
            <th aria-colindex="12">Homework 5</th>
            <th aria-colindex="13">Homework 6</th>
          </tr>
        </thead>
        <tbody>
          <tr aria-rowindex="50">
            <!--
              every cell needs to define the aria-colindex attribute
            -->
            <td aria-colindex="1">Henderson</td>
            <td aria-colindex="2">Alan</td>
            <td aria-colindex="10">8</td>
            <td aria-colindex="11">25</td>
            <td aria-colindex="12">9</td>
            <td aria-colindex="13">9</td>
          </tr>
          <tr aria-rowindex="51">
            <td aria-colindex="1">Henderson</td>
            <td aria-colindex="2">Alice</td>
            <td aria-colindex="10">10</td>
            <td aria-colindex="11">27</td>
            <td aria-colindex="12">10</td>
            <td aria-colindex="13">8</td>
          </tr>
          <tr aria-rowindex="52">
            <td aria-colindex="1">Henderson</td>
            <td aria-colindex="2">Andrew</td>
            <td aria-colindex="10">9</td>
            <td aria-colindex="11">0</td>
            <td aria-colindex="12">29</td>
            <td aria-colindex="13">8</td>
          </tr>
        </tbody>
      </table>
      <div role="grid" aria-rowcount="463">
        aria-label="Student grades for history 101"
        <div role="rowgroup">
          <div role="row" aria-rowindex="1">
              <!--
                aria-rowspan and aria-colspan provide
                assistive technologies with the correct data cell header information
                when header cells span more than one row or column.
              -->
              <span role="columnheader" aria-rowspan="2">Last Name</span>
              <span role="columnheader" aria-rowspan="2">First Name</span>
              <span role="columnheader" aria-colspan="2">Test 1</span>
              <span role="columnheader" aria-colspan="2">Test 2</span>
              <span role="columnheader" aria-colspan="2">Final</span>
          </div>
          <div role="row" aria-rowindex="2">
              <span role="columnheader">Score</span>
              <span role="columnheader">Grade</span>
              <span role="columnheader">Score</span>
              <span role="columnheader">Grade</span>
              <span role="columnheader">Total</span>
              <span role="columnheader">Grade</span>
          </div>
        </div>
        <div role="rowgroup">
          <div role="row" aria-rowindex="50">
            <span role="cell">Henderson</span>
            <span role="cell">Alan</span>
            <span role="cell">89</span>
            <span role="cell">B+</span>
            <span role="cell">72</span>
            <span role="cell">C</span>
            <span role="cell">161</span>
            <span role="cell">B-</span>
          </div>
          <div role="row"  aria-rowindex="51">
            <span role="cell">Henderson</span>
            <span role="cell">Alice</span>
            <span role="cell">94</span>
            <span role="cell">A</span>
            <span role="cell">86</span>
            <span role="cell">B</span>
            <span role="cell">180</span>
            <span role="cell">A-</span>
          </div>
          <div role="row"  aria-rowindex="52">
            <span role="cell">Henderson</span>
            <span role="cell">Andrew</span>
            <span role="cell">82</span>
            <span role="cell">B-</span>
            <span role="cell">95</span>
            <span role="cell">A</span>
            <span role="cell">177</span>
            <span role="cell">B+</span>
          </div>
        </div>
      </div>
      <table role="grid" aria-rowcount="463" aria-colcount="13"
        aria-label="Student grades for history 101">
        <thead>
          <tr aria-colindex="10" aria-rowindex="1">
            <th>Homework 4</th>
            <!--
              aria-sort indicates the column with the heading
              "Quiz 2" has been used to sort the rows of the grid.
            -->
            <th aria-sort="descending">Quiz 2</th>
            <th>Homework 5</th>
            <th>Homework 6</th>
          </tr>
        </thead>
        <tbody>
          <tr aria-colindex="10" aria-rowindex="50">
            <td>8</td>
            <td>30</td>
            <td>9</td>
            <td>9</td>
          </tr>
          <tr aria-colindex="10"  aria-rowindex="51">
            <td>10</td>
            <td>29</td>
            <td>10</td>
            <td>8</td>
          </tr>
          <tr aria-colindex="10"  aria-rowindex="52">
            <td>9</td>
            <td>9</td>
            <td>27</td>
            <td>6</td>
          </tr>
          <tr aria-colindex="10"  aria-rowindex="53">
            <td>9</td>
            <td>10</td>
            <td>26</td>
            <td>8</td>
          </tr>
          <tr aria-colindex="10"  aria-rowindex="54">
            <td>9</td>
            <td>7</td>
            <td>24</td>
            <td>6</td>
          </tr>
        </tbody>
      </table>
    <ul role="tablist">
      <li role="presentation">
        <a role="tab" href="#">Tab 1</a>
      </li>
      <li role="presentation">
        <a role="tab" href="#">Tab 2</a>
      </li>
      <li role="presentation">
        <a role="tab" href="#">Tab 3</a>
      </li>
    </ul>
    <ul role="presentation">
      <li>Date of birth:</li>
      <li>January 1, 3456</li>
    </ul>
    <div>Date of birth:</div>
      <div>January 1, 3456</div>
    <li role="tab"><h3>Title of My Tab</h3></li>
    <li role="tab"><h3 role="presentation">Title of My Tab</h3></li>
    <li role="tab">Title of My Tab</li>
    import Form from './Form';
    import './Form.css'
    function editTask(id, newName) {
      const editedTaskList = tasks.map(task => {
      // if this task has the same ID as the edited task
        if (id === task.id) {
          //
          return {...task, name: newName}
        }
        return task;
      });
      setTasks(editedTaskList);
    }
    const taskList = tasks.map(task => (
      <Todo
        id={task.id}
        name={task.name}
        completed={task.completed}
        key={task.id}
        toggleTaskCompleted={toggleTaskCompleted}
        deleteTask={deleteTask}
        editTask={editTask}
      />
    ));
    import React, { useState } from "react";
    const [isEditing, setEditing] = useState(false);
    const editingTemplate = (
      <form className="stack-small">
        <div className="form-group">
          <label className="todo-label" htmlFor={props.id}>
            New name for {props.name}
          </label>
          <input id={props.id} className="todo-text" type="text" />
        </div>
        <div className="btn-group">
          <button type="button" className="btn todo-cancel">
            Cancel
            <span className="visually-hidden">renaming {props.name}</span>
          </button>
          <button type="submit" className="btn btn__primary todo-edit">
            Save
            <span className="visually-hidden">new name for {props.name}</span>
          </button>
        </div>
      </form>
    );
    const viewTemplate = (
      <div className="stack-small">
        <div className="c-cb">
            <input
              id={props.id}
              type="checkbox"
              defaultChecked={props.completed}
              onChange={() => props.toggleTaskCompleted(props.id)}
            />
            <label className="todo-label" htmlFor={props.id}>
              {props.name}
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">{props.name}</span>
            </button>
            <button
              type="button"
              className="btn btn__danger"
              onClick={() => props.deleteTask(props.id)}
            >
              Delete <span className="visually-hidden">{props.name}</span>
            </button>
          </div>
      </div>
    );
    return <li className="todo">{isEditing ? editingTemplate : viewTemplate}</li>;
    <button type="button" className="btn" onClick={() => setEditing(true)}>
      Edit <span className="visually-hidden">{props.name}</span>
    </button>
    <button
      type="button"
      className="btn todo-cancel"
      onClick={() => setEditing(false)}
    >
      Cancel
      <span className="visually-hidden">renaming {props.name}</span>
    </button>
    const [newName, setNewName] = useState('');
    function handleChange(e) {
      setNewName(e.target.value);
    }
    <input
      id={props.id}
      className="todo-text"
      type="text"
      value={newName}
      onChange={handleChange}
    />
    function handleSubmit(e) {
      e.preventDefault();
      props.editTask(props.id, newName);
      setNewName("");
      setEditing(false);
    }
    <form className="stack-small" onSubmit={handleSubmit}>
    const [filter, setFilter] = useState('All');
    const FILTER_MAP = {
      All: () => true,
      Active: task => !task.completed,
      Completed: task => task.completed
    };
    const FILTER_NAMES = Object.keys(FILTER_MAP);
    const filterList = FILTER_NAMES.map(name => (
      <FilterButton key={name} name={name}/>
    ));
    <FilterButton />
    <FilterButton />
    <FilterButton />
    const filterList = FILTER_NAMES.map(name => (
      <FilterButton
        key={name}
        name={name}
        isPressed={name === filter}
        setFilter={setFilter}
      />
    ));
    function FilterButton(props) {
      return (
        <button
          type="button"
          className="btn toggle-btn"
          aria-pressed={props.isPressed}
          onClick={() => props.setFilter(props.name)}
        >
          <span className="visually-hidden">Show </span>
          <span>{props.name}</span>
          <span className="visually-hidden"> tasks</span>
        </button>
      );
    }
    const taskList = tasks
    .filter(FILTER_MAP[filter])
    .map(task => (
      <Todo
        id={task.id}
        name={task.name}
        completed={task.completed}
        key={task.id}
        toggleTaskCompleted={toggleTaskCompleted}
        deleteTask={deleteTask}
        editTask={editTask}
      />
    ));
    hashtag
    Composition

    The key feature of React is composition of components. Components written by different people should work well together. It is important to us that you can add functionality to a component without causing rippling changes throughout the codebase.

    For example, it should be possible to introduce some local state into a component without changing any of the components using it. Similarly, it should be possible to add some initialization and teardown code to any component when necessary.

    There is nothing "bad" about using state or lifecycle methods in components. Like any powerful feature, they should be used in moderation, but we have no intention to remove them. On the contrary, we think they are integral parts of what makes React useful. We might enable more functional patternsarrow-up-right in the future, but both local state and lifecycle methods will be a part of that model.

    Components are often described as "just functions" but in our view they need to be more than that to be useful. In React, components describe any composable behavior, and this includes rendering, lifecycle, and state. Some external libraries like Relayarrow-up-right augment components with other responsibilities such as describing data dependencies. It is possible that those ideas might make it back into React too in some form.

    hashtag
    Common Abstraction

    In general we resist adding featuresarrow-up-right that can be implemented in userland. We don't want to bloat your apps with useless library code. However, there are exceptions to this.

    For example, if React didn't provide support for local state or lifecycle methods, people would create custom abstractions for them. When there are multiple abstractions competing, React can't enforce or take advantage of the properties of either of them. It has to work with the lowest common denominator.

    This is why sometimes we add features to React itself. If we notice that many components implement a certain feature in incompatible or inefficient ways, we might prefer to bake it into React. We don't do it lightly. When we do it, it's because we are confident that raising the abstraction level benefits the whole ecosystem. State, lifecycle methods, cross-browser event normalization are good examples of this.

    We always discuss such improvement proposals with the community. You can find some of those discussions by the "big picture"arrow-up-right label on the React issue tracker.

    hashtag
    Escape Hatches

    React is pragmatic. It is driven by the needs of the products written at Facebook. While it is influenced by some paradigms that are not yet fully mainstream such as functional programming, staying accessible to a wide range of developers with different skills and experience levels is an explicit goal of the project.

    If we want to deprecate a pattern that we don't like, it is our responsibility to consider all existing use cases for it and educate the community about the alternatives before we deprecate it. If some pattern that is useful for building apps is hard to express in a declarative way, we will provide an imperative API for it. If we can't figure out a perfect API for something that we found necessary in many apps, we will provide a temporary subpar working API as long as it is possible to get rid of it later and it leaves the door open for future improvements.

    hashtag
    Stability

    We value API stability. At Facebook, we have more than 50 thousand components using React. Many other companies, including Twitterarrow-up-right and Airbnbarrow-up-right, are also heavy users of React. This is why we are usually reluctant to change public APIs or behavior.

    However we think stability in the sense of "nothing changes" is overrated. It quickly turns into stagnation. Instead, we prefer the stability in the sense of "It is heavily used in production, and when something changes, there is a clear (and preferably automated) migration path."

    When we deprecate a pattern, we study its internal usage at Facebook and add deprecation warnings. They let us assess the impact of the change. Sometimes we back out if we see that it is too early, and we need to think more strategically about getting the codebases to the point where they are ready for this change.

    If we are confident that the change is not too disruptive and the migration strategy is viable for all use cases, we release the deprecation warning to the open source community. We are closely in touch with many users of React outside of Facebook, and we monitor popular open source projects and guide them in fixing those deprecations.

    Given the sheer size of the Facebook React codebase, successful internal migration is often a good indicator that other companies won't have problems either. Nevertheless sometimes people point out additional use cases we haven't thought of, and we add escape hatches for them or rethink our approach.

    We don't deprecate anything without a good reason. We recognize that sometimes deprecations warnings cause frustration but we add them because deprecations clean up the road for the improvements and new features that we and many people in the community consider valuable.

    For example, we added a warning about unknown DOM props in React 15.2.0. Many projects were affected by this. However fixing this warning is important so that we can introduce the support for custom attributesarrow-up-right to React. There is a reason like this behind every deprecation that we add.

    When we add a deprecation warning, we keep it for the rest of the current major version, and change the behavior in the next major version. If there is a lot of repetitive manual work involved, we release a codemodarrow-up-right script that automates most of the change. Codemods enable us to move forward without stagnation in a massive codebase, and we encourage you to use them as well.

    You can find the codemods that we released in the react-codemodarrow-up-right repository.

    hashtag
    Interoperability

    We place high value in interoperability with existing systems and gradual adoption. Facebook has a massive non-React codebase. Its website uses a mix of a server-side component system called XHP, internal UI libraries that came before React, and React itself. It is important to us that any product team can start using React for a small featurearrow-up-right rather than rewrite their code to bet on it.

    This is why React provides escape hatches to work with mutable models, and tries to work well together with other UI libraries. You can wrap an existing imperative UI into a declarative component, and vice versa. This is crucial for gradual adoption.

    hashtag
    Scheduling

    Even when your components are described as functions, when you use React you don't call them directly. Every component returns a description of what needs to be rendered, and that description may include both user-written components like <LikeButton> and platform-specific components like <div>. It is up to React to "unroll" <LikeButton> at some point in the future and actually apply changes to the UI tree according to the render results of the components recursively.

    This is a subtle distinction but a powerful one. Since you don't call that component function but let React call it, it means React has the power to delay calling it if necessary. In its current implementation React walks the tree recursively and calls render functions of the whole updated tree during a single tick. However in the future it might start delaying some updates to avoid dropping framesarrow-up-right.

    This is a common theme in React design. Some popular libraries implement the "push" approach where computations are performed when the new data is available. React, however, sticks to the "pull" approach where computations can be delayed until necessary.

    React is not a generic data processing library. It is a library for building user interfaces. We think that it is uniquely positioned in an app to know which computations are relevant right now and which are not.

    If something is offscreen, we can delay any logic related to it. If data is arriving faster than the frame rate, we can coalesce and batch updates. We can prioritize work coming from user interactions (such as an animation caused by a button click) over less important background work (such as rendering new content just loaded from the network) to avoid dropping frames.

    To be clear, we are not taking advantage of this right now. However the freedom to do something like this is why we prefer to have control over scheduling, and why setState() is asynchronous. Conceptually, we think of it as "scheduling an update".

    The control over scheduling would be harder for us to gain if we let the user directly compose views with a "push" based paradigm common in some variations of Functional Reactive Programmingarrow-up-right. We want to own the "glue" code.

    It is a key goal for React that the amount of the user code that executes before yielding back into React is minimal. This ensures that React retains the capability to schedule and split work in chunks according to what it knows about the UI.

    There is an internal joke in the team that React should have been called "Schedule" because React does not want to be fully "reactive".

    hashtag
    Developer Experience

    Providing a good developer experience is important to us.

    For example, we maintain React DevToolsarrow-up-right which let you inspect the React component tree in Chrome and Firefox. We have heard that it brings a big productivity boost both to the Facebook engineers and to the community.

    We also try to go an extra mile to provide helpful developer warnings. For example, React warns you in development if you nest tags in a way that the browser doesn't understand, or if you make a common typo in the API. Developer warnings and the related checks are the main reason why the development version of React is slower than the production version.

    The usage patterns that we see internally at Facebook help us understand what the common mistakes are, and how to prevent them early. When we add new features, we try to anticipate the common mistakes and warn about them.

    We are always looking out for ways to improve the developer experience. We love to hear your suggestions and accept your contributions to make it even better.

    hashtag
    Debugging

    When something goes wrong, it is important that you have breadcrumbs to trace the mistake to its source in the codebase. In React, props and state are those breadcrumbs.

    If you see something wrong on the screen, you can open React DevTools, find the component responsible for rendering, and then see if the props and state are correct. If they are, you know that the problem is in the component’s render() function, or some function that is called by render(). The problem is isolated.

    If the state is wrong, you know that the problem is caused by one of the setState() calls in this file. This, too, is relatively simple to locate and fix because usually there are only a few setState() calls in a single file.

    If the props are wrong, you can traverse the tree up in the inspector, looking for the component that first "poisoned the well" by passing bad props down.

    This ability to trace any UI to the data that produced it in the form of current props and state is very important to React. It is an explicit design goal that state is not "trapped" in closures and combinators, and is available to React directly.

    While the UI is dynamic, we believe that synchronous render() functions of props and state turn debugging from guesswork into a boring but finite procedure. We would like to preserve this constraint in React even though it makes some use cases, like complex animations, harder.

    hashtag
    Configuration

    We find global runtime configuration options to be problematic.

    For example, it is occasionally requested that we implement a function like React.configure(options) or React.register(component). However this poses multiple problems, and we are not aware of good solutions to them.

    What if somebody calls such a function from a third-party component library? What if one React app embeds another React app, and their desired configurations are incompatible? How can a third-party component specify that it requires a particular configuration? We think that global configuration doesn't work well with composition. Since composition is central to React, we don't provide global configuration in code.

    We do, however, provide some global configuration on the build level. For example, we provide separate development and production builds. We may also add a profiling buildarrow-up-right in the future, and we are open to considering other build flags.

    hashtag
    Beyond the DOM

    We see the value of React in the way it allows us to write components that have fewer bugs and compose together well. DOM is the original rendering target for React but React Nativearrow-up-right is just as important both to Facebook and the community.

    Being renderer-agnostic is an important design constraint of React. It adds some overhead in the internal representations. On the other hand, any improvements to the core translate across platforms.

    Having a single programming model lets us form engineering teams around products instead of platforms. So far the tradeoff has been worth it for us.

    hashtag
    Implementation

    We try to provide elegant APIs where possible. We are much less concerned with the implementation being elegant. The real world is far from perfect, and to a reasonable extent we prefer to put the ugly code into the library if it means the user does not have to write it. When we evaluate new code, we are looking for an implementation that is correct, performant and affords a good developer experience. Elegance is secondary.

    We prefer boring code to clever code. Code is disposable and often changes. So it is important that it doesn't introduce new internal abstractions unless absolutely necessaryarrow-up-right. Verbose code that is easy to move around, change and remove is preferred to elegant code that is prematurely abstracted and hard to change.

    hashtag
    Optimized for Tooling

    Some commonly used APIs have verbose names. For example, we use componentDidMount() instead of didMount() or onMount(). This is intentionalarrow-up-right. The goal is to make the points of interaction with the library highly visible.

    In a massive codebase like Facebook, being able to search for uses of specific APIs is very important. We value distinct verbose names, and especially for the features that should be used sparingly. For example, dangerouslySetInnerHTML is hard to miss in a code review.

    Optimizing for search is also important because of our reliance on codemodsarrow-up-right to make breaking changes. We want it to be easy and safe to apply vast automated changes across the codebase, and unique verbose names help us achieve this. Similarly, distinctive names make it easy to write custom lint rulesarrow-up-right about using React without worrying about potential false positives.

    JSX plays a similar role. While it is not required with React, we use it extensively at Facebook both for aesthetic and pragmatic reasons.

    In our codebase, JSX provides an unambiguous hint to the tools that they are dealing with a React element tree. This makes it possible to add build-time optimizations such as hoisting constant elementsarrow-up-right, safely lint and codemod internal component usage, and include JSX source locationarrow-up-right into the warnings.

    hashtag
    Dogfooding

    We try our best to address the problems raised by the community. However we are likely to prioritize the issues that people are also experiencing internally at Facebook. Perhaps counter-intuitively, we think this is the main reason why the community can bet on React.

    Heavy internal usage gives us the confidence that React won't disappear tomorrow. React was created at Facebook to solve its problems. It brings tangible business value to the company and is used in many of its products. Dogfoodingarrow-up-right it means that our vision stays sharp and we have a focused direction going forward.

    This doesn't mean that we ignore the issues raised by the community. For example, we added support for web components and SVGarrow-up-right to React even though we don't rely on either of them internally. We are actively listening to your pain pointsarrow-up-right and address them to the best of our ability. The community is what makes React special to us, and we are honored to contribute back.

    After releasing many open source projects at Facebook, we have learned that trying to make everyone happy at the same time produced projects with poor focus that didn't grow well. Instead, we found that picking a small audience and focusing on making them happy brings a positive net effect. That's exactly what we did with React, and so far solving the problems encountered by Facebook product teams has translated well to the open source community.

    The downside of this approach is that sometimes we fail to give enough focus to the things that Facebook teams don't have to deal with, such as the "getting started" experience. We are acutely aware of this, and we are thinking of how to improve in a way that would benefit everyone in the community without making the same mistakes we did with open source projects before.

    []
    ) if no elements match.
  • findAllBy...: Returns a promise which resolves to an array of elements when any elements are found which match the given query. The promise is rejected if no elements are found after a default timeout of 1000ms.

    • findBy methods are a combination of getBy* queries and waitForarrow-up-right. They accept the waitFor options as the last argument (i.e. await screen.findByText('text', queryOptions, waitForOptions))

  • getByTitle: The title attribute is not consistently read by screenreaders, and is not visible by default for sighted users

    Throw error

    No

    queryBy...

    Return null

    Return element

    Throw error

    No

    findBy...

    Throw error

    Return element

    Throw error

    Yes

    Multiple Elements

    getAllBy...

    Throw error

    Return array

    Return array

    No

    queryAllBy...

    Return []

    Return array

    Return array

    No

    findAllBy...

    Throw error

    Return array

    Return array

    Yes

    list of rolesarrow-up-right
    A placeholder is not a substitute for a labelarrow-up-right
    Normalizationarrow-up-right

    Typescript Tricks

    DNT-2658

    Components must receive focus in an order that preserves meaning and operability

    Jira Tickets

    DNT-2658 Components must receive focus in an order that preserves meaning and operability

    This example renders a different greeting depending on the value of isLoggedIn prop.

    hashtag
    Element Variables

    You can use variables to store elements. This can help you conditionally render a part of the component while the rest of the output doesn't change.

    Consider these two new components representing Logout and Login buttons:

    In the example below, we will create a stateful component called LoginControl.

    It will render either <LoginButton /> or <LogoutButton /> depending on its current state. It will also render a <Greeting /> from the previous example:

    Try it on CodePenarrow-up-right

    While declaring a variable and using an if statement is a fine way to conditionally render a component, sometimes you might want to use a shorter syntax. There are a few ways to inline conditions in JSX, explained below.

    hashtag
    Inline If with Logical && Operator

    You may embed expressions in JSX by wrapping them in curly braces. This includes the JavaScript logical && operator. It can be handy for conditionally including an element:

    Try it on CodePenarrow-up-right

    It works because in JavaScript, true && expression always evaluates to expression, and false && expression always evaluates to false.

    Therefore, if the condition is true, the element right after && will appear in the output. If it is false, React will ignore and skip it.

    Note that returning a falsy expression will still cause the element after && to be skipped but will return the falsy expression. In the example below, <div>0</div> will be returned by the render method.

    hashtag
    Inline If-Else with Conditional Operator

    Another method for conditionally rendering elements inline is to use the JavaScript conditional operator condition ? true : falsearrow-up-right.

    In the example below, we use it to conditionally render a small block of text.

    It can also be used for larger expressions although it is less obvious what's going on:

    Just like in JavaScript, it is up to you to choose an appropriate style based on what you and your team consider more readable. Also remember that whenever conditions become too complex, it might be a good time to extract a component.

    hashtag
    Preventing Component from Rendering

    In rare cases you might want a component to hide itself even though it was rendered by another component. To do this return null instead of its render output.

    In the example below, the <WarningBanner /> is rendered depending on the value of the prop called warn. If the value of the prop is false, then the component does not render:

    Try it on CodePenarrow-up-right

    Returning null from a component's render method does not affect the firing of the component's lifecycle methods. For instance componentDidUpdate will still be called.

    ifarrow-up-right
    conditional operatorarrow-up-right
    Try it on CodePenarrow-up-right
    In HTML, form elements such as
    <input>
    ,
    <textarea>
    , and
    <select>
    typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with
    setState()
    .

    We can combine the two by making the React state be the "single source of truth". Then the React component that renders a form also controls what happens in that form on subsequent user input. An input form element whose value is controlled by React in this way is called a "controlled component".

    For example, if we want to make the previous example log the name when it is submitted, we can write the form as a controlled component:

    Try it on CodePenarrow-up-right

    Since the value attribute is set on our form element, the displayed value will always be this.state.value, making the React state the source of truth. Since handleChange runs on every keystroke to update the React state, the displayed value will update as the user types.

    With a controlled component, the input's value is always driven by the React state. While this means you have to type a bit more code, you can now pass the value to other UI elements too, or reset it from other event handlers.

    hashtag
    The textarea Tag

    In HTML, a <textarea> element defines its text by its children:

    In React, a <textarea> uses a value attribute instead. This way, a form using a <textarea> can be written very similarly to a form that uses a single-line input:

    Notice that this.state.value is initialized in the constructor, so that the text area starts off with some text in it.

    hashtag
    The select Tag

    In HTML, <select> creates a drop-down list. For example, this HTML creates a drop-down list of flavors:

    Note that the Coconut option is initially selected, because of the selected attribute. React, instead of using this selected attribute, uses a value attribute on the root select tag. This is more convenient in a controlled component because you only need to update it in one place. For example:

    Try it on CodePenarrow-up-right

    Overall, this makes it so that <input type="text">, <textarea>, and <select> all work very similarly - they all accept a value attribute that you can use to implement a controlled component.

    Note

    You can pass an array into the value attribute, allowing you to select multiple options in a select tag:

    hashtag
    The file input Tag

    In HTML, an <input type="file"> lets the user choose one or more files from their device storage to be uploaded to a server or manipulated by JavaScript via the File APIarrow-up-right.

    Because its value is read-only, it is an uncontrolled component in React. It is discussed together with other uncontrolled components later in the documentation.

    hashtag
    Handling Multiple Inputs

    When you need to handle multiple controlled input elements, you can add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name.

    For example:

    Try it on CodePenarrow-up-right

    Note how we used the ES6 computed property namearrow-up-right syntax to update the state key corresponding to the given input name:

    It is equivalent to this ES5 code:

    Also, since setState() automatically merges a partial state into the current state, we only needed to call it with the changed parts.

    hashtag
    Controlled Input Null Value

    Specifying the value prop on a controlled component prevents the user from changing the input unless you desire so. If you've specified a value but the input is still editable, you may have accidentally set value to undefined or null.

    The following code demonstrates this. (The input is locked at first but becomes editable after a short delay.)

    hashtag
    Alternatives to Controlled Components

    It can sometimes be tedious to use controlled components, because you need to write an event handler for every way your data can change and pipe all of the input state through a React component. This can become particularly annoying when you are converting a preexisting codebase to React, or integrating a React application with a non-React library. In these situations, you might want to check out uncontrolled components, an alternative technique for implementing input forms.

    hashtag
    Fully-Fledged Solutions

    If you're looking for a complete solution including validation, keeping track of the visited fields, and handling form submission, Formikarrow-up-right is one of the popular choices. However, it is built on the same principles of controlled components and managing state — so don't neglect to learn them.

    This section documents a legacy API. See the new API.

    Suppose you have a structure like:

    In this example, we manually thread through a color prop in order to style the Button and Message components appropriately. Using context, we can pass this through the tree automatically:

    By adding childContextTypes and getChildContext to MessageList (the context provider), React passes the information down automatically and any component in the subtree (in this case, Button) can access it by defining contextTypes.

    If contextTypes is not defined, then context will be an empty object.

    Note:

    React.PropTypes has moved into a different package since React v15.5. Please use the prop-types library insteadarrow-up-right to define contextTypes.

    We provide a codemod script to automate the conversion.

    hashtag
    Parent-Child Coupling

    This section documents a legacy API. See the new API.

    Context can also let you build an API where parents and children communicate. For example, one library that works this way is React Router V4arrow-up-right:

    By passing down some information from the Router component, each Link and Route can communicate back to the containing Router.

    Before you build components with an API similar to this, consider if there are cleaner alternatives. For example, you can pass entire React components as props if you'd like to.

    hashtag
    Referencing Context in Lifecycle Methods

    This section documents a legacy API. See the new API.

    If contextTypes is defined within a component, the following lifecycle methods will receive an additional parameter, the context object:

    • constructor(props, context)

    • componentWillReceiveProps(nextProps, nextContext)

    • shouldComponentUpdate(nextProps, nextState, nextContext)

    • componentWillUpdate(nextProps, nextState, nextContext)

    Note:

    As of React 16, componentDidUpdate no longer receives prevContext.

    hashtag
    Referencing Context in Function Components

    This section documents a legacy API. See the new API.

    Function components are also able to reference context if contextTypes is defined as a property of the function. The following code shows a Button component written as a function component.

    hashtag
    Updating Context

    This section documents a legacy API. See the new API.

    Don't do it.

    React has an API to update context, but it is fundamentally broken and you should not use it.

    The getChildContext function will be called when the state or props changes. In order to update data in the context, trigger a local state update with this.setState. This will trigger a new context and changes will be received by the children.

    The problem is, if a context value provided by component changes, descendants that use that value won't update if an intermediate parent returns false from shouldComponentUpdate. This is totally out of control of the components using context, so there's basically no way to reliably update the context. This blog postarrow-up-right has a good explanation of why this is a problem and how you might get around it.

    function UserGreeting(props) {
      return <h1>Welcome back!</h1>;
    }
    
    function GuestGreeting(props) {
      return <h1>Please sign up.</h1>;
    }
    function Greeting(props) {
      const isLoggedIn = props.isLoggedIn;
      if (isLoggedIn) {
        return <UserGreeting />;
      }
      return <GuestGreeting />;
    }
    
    ReactDOM.render(
      // Try changing to isLoggedIn={true}:
      <Greeting isLoggedIn={false} />,
      document.getElementById('root')
    );
    function LoginButton(props) {
      return <button onClick={props.onClick}>Login</button>;
    }
    
    function LogoutButton(props) {
      return <button onClick={props.onClick}>Logout</button>;
    }
    class LoginControl extends React.Component {
      constructor(props) {
        super(props);
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
        this.state = {isLoggedIn: false};
      }
    
      handleLoginClick() {
        this.setState({isLoggedIn: true});
      }
    
      handleLogoutClick() {
        this.setState({isLoggedIn: false});
      }
    
      render() {
        const isLoggedIn = this.state.isLoggedIn;
        let button;
    
        if (isLoggedIn) {
          button = <LogoutButton onClick={this.handleLogoutClick} />;
        } else {
          button = <LoginButton onClick={this.handleLoginClick} />;
        }
    
        return (
          <div>
            <Greeting isLoggedIn={isLoggedIn} />
            {button}
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <LoginControl />,
      document.getElementById('root')
    );
    function Mailbox(props) {
      const unreadMessages = props.unreadMessages;
      return (
        <div>
          <h1>Hello!</h1>
          {unreadMessages.length > 0 &&
            <h2>
              You have {unreadMessages.length} unread messages.
            </h2>
          }
        </div>
      );
    }
    
    const messages = ['React', 'Re: React', 'Re:Re: React'];
    ReactDOM.render(
      <Mailbox unreadMessages={messages} />,
      document.getElementById('root')
    );
    render() {
      const count = 0;
      return (
        <div>
          { count && <h1>Messages: {count}</h1>}
        </div>
      );
    }
    render() {
      const isLoggedIn = this.state.isLoggedIn;
      return (
        <div>
          The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
        </div>
      );
    }
    render() {
      const isLoggedIn = this.state.isLoggedIn;
      return (
        <div>
          {isLoggedIn
            ? <LogoutButton onClick={this.handleLogoutClick} />
            : <LoginButton onClick={this.handleLoginClick} />
          }
        </div>
      );
    }
    function WarningBanner(props) {
      if (!props.warn) {
        return null;
      }
    
      return (
        <div className="warning">
          Warning!
        </div>
      );
    }
    
    class Page extends React.Component {
      constructor(props) {
        super(props);
        this.state = {showWarning: true};
        this.handleToggleClick = this.handleToggleClick.bind(this);
      }
    
      handleToggleClick() {
        this.setState(state => ({
          showWarning: !state.showWarning
        }));
      }
    
      render() {
        return (
          <div>
            <WarningBanner warn={this.state.showWarning} />
            <button onClick={this.handleToggleClick}>
              {this.state.showWarning ? 'Hide' : 'Show'}
            </button>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Page />,
      document.getElementById('root')
    );
    <select multiple={true} value={['B', 'C']}>
    <form>
      <label>
        Name:
        <input type="text" name="name" />
      </label>
      <input type="submit" value="Submit" />
    </form>
    class NameForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {value: ''};
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('A name was submitted: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Name:
              <input type="text" value={this.state.value} onChange={this.handleChange} />
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    <textarea>
      Hello there, this is some text in a text area
    </textarea>
    class EssayForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          value: 'Please write an essay about your favorite DOM element.'
        };
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('An essay was submitted: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Essay:
              <textarea value={this.state.value} onChange={this.handleChange} />
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    <select>
      <option value="grapefruit">Grapefruit</option>
      <option value="lime">Lime</option>
      <option selected value="coconut">Coconut</option>
      <option value="mango">Mango</option>
    </select>
    class FlavorForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {value: 'coconut'};
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('Your favorite flavor is: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Pick your favorite flavor:
              <select value={this.state.value} onChange={this.handleChange}>
                <option value="grapefruit">Grapefruit</option>
                <option value="lime">Lime</option>
                <option value="coconut">Coconut</option>
                <option value="mango">Mango</option>
              </select>
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    <input type="file" />
    class Reservation extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          isGoing: true,
          numberOfGuests: 2
        };
    
        this.handleInputChange = this.handleInputChange.bind(this);
      }
    
      handleInputChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
    
        this.setState({
          [name]: value
        });
      }
    
      render() {
        return (
          <form>
            <label>
              Is going:
              <input
                name="isGoing"
                type="checkbox"
                checked={this.state.isGoing}
                onChange={this.handleInputChange} />
            </label>
            <br />
            <label>
              Number of guests:
              <input
                name="numberOfGuests"
                type="number"
                value={this.state.numberOfGuests}
                onChange={this.handleInputChange} />
            </label>
          </form>
        );
      }
    }
    this.setState({
      [name]: value
    });
    var partialState = {};
    partialState[name] = value;
    this.setState(partialState);
    ReactDOM.render(<input value="hi" />, mountNode);
    
    setTimeout(function () {
      ReactDOM.render(<input value={null} />, mountNode);
    }, 1000);
    class Button extends React.Component {
      render() {
        return (
          <button style={{ background: this.props.color }}>
            {this.props.children}
          </button>
        );
      }
    }
    
    class Message extends React.Component {
      render() {
        return (
          <div>
            {this.props.text} <Button color={this.props.color}>Delete</Button>
          </div>
        );
      }
    }
    
    class MessageList extends React.Component {
      render() {
        const color = "purple";
        const children = this.props.messages.map((message) => (
          <Message text={message.text} color={color} />
        ));
        return <div>{children}</div>;
      }
    }
    import PropTypes from 'prop-types';
    
    class Button extends React.Component {
      render() {
        return (
          <button style={{background: this.context.color}}>
            {this.props.children}
          </button>
        );
      }
    }
    
    Button.contextTypes = {
      color: PropTypes.string
    };
    
    class Message extends React.Component {
      render() {
        return (
          <div>
            {this.props.text} <Button>Delete</Button>
          </div>
        );
      }
    }
    
    class MessageList extends React.Component {
      getChildContext() {
        return {color: "purple"};
      }
    
      render() {
        const children = this.props.messages.map((message) =>
          <Message text={message.text} />
        );
        return <div>{children}</div>;
      }
    }
    
    MessageList.childContextTypes = {
      color: PropTypes.string
    };
    import { BrowserRouter as Router, Route, Link } from "react-router-dom";
    
    const BasicExample = () => (
      <Router>
        <div>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/topics">Topics</Link>
            </li>
          </ul>
    
          <hr />
    
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/topics" component={Topics} />
        </div>
      </Router>
    );
    import PropTypes from "prop-types";
    
    const Button = ({ children }, context) => (
      <button style={{ background: context.color }}>{children}</button>
    );
    
    Button.contextTypes = { color: PropTypes.string };
    import PropTypes from "prop-types";
    
    class MediaQuery extends React.Component {
      constructor(props) {
        super(props);
        this.state = { type: "desktop" };
      }
    
      getChildContext() {
        return { type: this.state.type };
      }
    
      componentDidMount() {
        const checkMediaQuery = () => {
          const type = window.matchMedia("(min-width: 1025px)").matches
            ? "desktop"
            : "mobile";
          if (type !== this.state.type) {
            this.setState({ type });
          }
        };
    
        window.addEventListener("resize", checkMediaQuery);
        checkMediaQuery();
      }
    
      render() {
        return this.props.children;
      }
    }
    
    MediaQuery.childContextTypes = {
      type: PropTypes.string,
    };
    3.3 Alert and Message Dialogsarrow-up-right
  • 3.4 Breadcrumbarrow-up-right

  • 3.5 Buttonarrow-up-right

  • 3.6 Carousel (Slide Show or Image Rotator)arrow-up-right

  • 3.7 Checkboxarrow-up-right

  • 3.8 Combo Boxarrow-up-right

  • 3.9 Dialog (Modal)arrow-up-right

  • 3.10 Disclosure (Show/Hide)arrow-up-right

  • 3.11 Feedarrow-up-right

  • 3.12 Grids : Interactive Tabular Data and Layout Containersarrow-up-right

  • 3.13 Linkarrow-up-right

  • 3.14 Listboxarrow-up-right

  • 3.15 Menu or Menu bararrow-up-right

  • 3.16 Menu Buttonarrow-up-right

  • 3.17 Radio Grouparrow-up-right

  • 3.18 Sliderarrow-up-right

  • 3.19 Slider (Multi-Thumb)arrow-up-right

  • 3.20 Spinbuttonarrow-up-right

  • 3.21 Tablearrow-up-right

  • 3.22 Tabsarrow-up-right

  • 3.23 Toolbararrow-up-right

  • 3.24 Tooltip Widgetarrow-up-right

  • 3.25 Tree Viewarrow-up-right

  • 3.26 Treegridarrow-up-right

  • 3.27 Window Splitterarrow-up-right

  • 4.3 Landmark Rolesarrow-up-right
    1. 4.3.1 Bannerarrow-up-right

    2. 4.3.2 Complementaryarrow-up-right

    3. 4.3.3 Contentinfoarrow-up-right

    5.3 Accessible Namesarrow-up-right
    1. 5.3.1 Cardinal Rules of Namingarrow-up-right

      1. 5.3.1.1 Rule 1: Heed Warnings and Test Thoroughlyarrow-up-right

      2. 5.3.1.2 Rule 2: Prefer Visible Textarrow-up-right

  • 5.4 Accessible Descriptionsarrow-up-right

    1. 5.4.1 Describing Techniquesarrow-up-right

      1. 5.4.1.1 Describing by referencing content with aria-describedbyarrow-up-right

  • 6.3 Focus VS Selection and the Perception of Dual Focusarrow-up-right
  • 6.4 Deciding When to Make Selection Automatically Follow Focusarrow-up-right

  • 6.5 Keyboard Navigation Between Components (The Tab Sequence)arrow-up-right

  • 6.6 Keyboard Navigation Inside Componentsarrow-up-right

    1. 6.6.1 Managing Focus Within Components Using a Roving tabindexarrow-up-right

    2. 6.6.2 Managing Focus in Composites Using aria-activedescendantarrow-up-right

  • 6.7 Focusability of disabled controlsarrow-up-right

  • 6.8 Key Assignment Conventions for Common Functionsarrow-up-right

  • 6.9 Keyboard Shortcutsarrow-up-right

    1. 6.9.1 Designing the Scope and Behavior of Keyboard Shortcutsarrow-up-right

      1. 6.9.1.1 Ensure Basic Access Via Navigationarrow-up-right

  • 7.2.1 Using aria-colindex When Column Indices Are Contiguousarrow-up-right

  • 7.2.2 Using aria-colindex When Column Indices Are Not Contiguousarrow-up-right

  • 7.3 Defining cell spans using aria-colspan and aria-rowspanarrow-up-right

  • 7.4 Indicating sort order with aria-sortarrow-up-right

  • 8.3 Example Demonstrating Effects of the presentation Rolearrow-up-right
    Role region is especially helpful to the perception of structure by screen reader users when panels contain heading elements or a nested accordion.

    If unique names that identify the slide content are not available, a number and set size can serve as a meaningful alternative, e.g., "3 of 10". Note: Normally, including set position and size information in an accessible name is not appropriate. An exception is helpful in this implementation because group elements do not support aria-setsizearrow-up-right or aria-posinsetarrow-up-right. The tabbed carousel implementation pattern does not have this limitation.

  • Note that since the aria-roledescription is set to "slide", the label does not contain the word "slide."

  • polite
    : if the carousel is
    NOT
    automatically rotating.

    Vertical arrow key navigation typically wraps from one column to another.

  • If all cells in a row contain information about the same suggested value:

    • Either the row containing focus is selected or a cell containing a suggested value is selected when any cell in the same row contains focus.

    • Horizontal key navigation may wrap from one row to another.

    • Vertical arrow key navigation does not wrap from one column to another.

  • When focus is on an end node, does nothing.

    When focus is on a root node that is also either an end node or a closed node, does nothing.

    the element with role combobox has a value for aria-haspopuparrow-up-right of listbox. Note that elements with role combobox have an implicit aria-haspopup value of listbox.

    : When the popup is triggered, it presents suggested values that complete or logically correspond to the characters typed in the textbox.
  • both: When the popup is triggered, it presents suggested values that complete or logically correspond to the characters typed in the textbox. In addition, the portion of the selected suggestion that has not been typed by the user, known as the completion string, appears inline after the input cursor in the textbox. The inline completion string is visually highlighted and has a selected state.

  • It is very unlikely users need to immediately re-invoke the dialog.

  • The task completed in the dialog is directly related to a subsequent step in the work flow.For example, a grid has an associated toolbar with a button for adding rows. the Add Rows button opens a dialog that prompts for the number of rows. After the dialog closes, focus is placed in the first cell of the first new row.

  • The dialog element is not a descendant of any element that has aria-hidden set to true.

    Providing reading mode keys for moving the reading cursor and DOM focus past the end and before the start of the feed.
    is set to the position of a cell within a row or column, respectively.
    Space: changes the selection state of the focused option.
  • Shift + Down Arrow (Optional): Moves focus to and toggles the selected state of the next option.

  • Shift + Up Arrow (Optional): Moves focus to and toggles the selected state of the previous option.

  • Shift + Space (Optional): Selects contiguous items from the most recently selected item to the focused item.

  • Control + Shift + Home (Optional): Selects the focused option and all options up to the first option. Optionally, moves focus to the first option.

  • Control + Shift + End (Optional): Selects the focused option and all options down to the last option. Optionally, moves focus to the last option.

  • Control + A (Optional): Selects all options in the list. Optionally, if all options are selected, it may also unselect all options.

  • Alternative selection model -- moving focus without holding a Shift or Control modifier unselects all selected nodes except the focused node:

    • Shift + Down Arrow: Moves focus to and toggles the selection state of the next option.

    • Shift + Up Arrow: Moves focus to and toggles the selection state of the previous option.

    • Control + Down Arrow: Moves focus to the next option without changing its selection state.

    • Control + Up Arrow: Moves focus to the previous option without changing its selection state.

    • Control + Space Changes the selection state of the focused option.

    • Shift + Space (Optional): Selects contiguous items from the most recently selected item to the focused item.

    • Control + Shift + Home (Optional): Selects the focused option and all options up to the first option. Optionally, moves focus to the first option.

    • Control + Shift + End (Optional): Selects the focused option and all options down to the last option. Optionally, moves focus to the last option.

    • Control + A (Optional): Selects all options in the list. Optionally, if all options are selected, it may also unselect all options.

  • set to
    true
    .
  • All options that are not selected have aria-selectedarrow-up-right set to false.

  • When focus is on the first radio button in the radio group and that radio button is also the first element in the toolbar, moves focus to the last element in the toolbar.

    End (Optional): Moves focus to the last tab. Optionally, activates the newly focused tab (See note below).

  • Shift + F10: If the tab has an associated pop-up menu, opens the menu.

  • Delete (Optional): If deletion is allowed, deletes (closes) the current tab element and its associated tab panel, sets focus on the tab following the tab that was closed, and optionally activates the newly focused tab. If there is not a tab that followed the tab that was deleted, e.g., the deleted tab was the right-most tab in a left-to-right horizontal tab list, sets focus on and optionally activates the tab that preceded the deleted tab. If the application allows all tabs to be deleted, and the user deletes the last remaining tab in the tab list, the application moves focus to another element that provides a logical work flow. As an alternative to Delete, or in addition to supporting Delete, the delete function is available in a context menu.

  • When focus is on an end node, does nothing.
    When focus is on a root node that is also either an end node or a closed node, does nothing.
    Space: Toggles the selection state of the focused node.
  • Shift + Down Arrow (Optional): Moves focus to and toggles the selection state of the next node.

  • Shift + Up Arrow (Optional): Moves focus to and toggles the selection state of the previous node.

  • Shift + Space (Optional): Selects contiguous nodes from the most recently selected node to the current node.

  • Control + Shift + Home (Optional): Selects the node with focus and all nodes up to the first node. Optionally, moves focus to the first node.

  • Control + Shift + End (Optional): Selects the node with focus and all nodes down to the last node. Optionally, moves focus to the last node.

  • Control + A (Optional): Selects all nodes in the tree. Optionally, if all nodes are selected, it can also unselect all nodes.

  • Alternative selection model -- Moving focus without holding the Shift or Control modifier unselects all selected nodes except for the focused node:

    • Shift + Down Arrow: Moves focus to and toggles the selection state of the next node.

    • Shift + Up Arrow: Moves focus to and toggles the selection state of the previous node.

    • Control + Down Arrow: Without changing the selection state, moves focus to the next node.

    • Control + Up Arrow: Without changing the selection state, moves focus to the previous node.

    • Control + Space: Toggles the selection state of the focused node.

    • Shift + Space (Optional): Selects contiguous nodes from the most recently selected node to the current node.

    • Control + Shift + Home (Optional): Selects the node with focus and all nodes up to the first node. Optionally, moves focus to the first node.

    • Control + Shift + End (Optional): Selects the node with focus and all nodes down to the last node. Optionally, moves focus to the last node.

    • Control + A (Optional): Selects all nodes in the tree. Optionally, if all nodes are selected, it can also unselect all nodes.

  • .
  • If the tree contains nodes that are not selectable, those nodes do not have the aria-selected state.

  • If focus is on any other cell, moves focus one cell to the right.

    If focus is on the first cell in a row and row focus is supported, moves focus to the row.
  • If focus is on the first cell in a row and row focus is not supported, focus does not move.

  • If focus is on any other cell, moves focus one cell to the left.

  • .
    is set to the position of a cell within a row or column, respectively.

    nav

  • section

  • select

  • textarea

  • Descriptions derived from titles.

    attribute.

    input whose type attribute is in the Submit Button state

    A localized string of the word "submit".

    input whose type attribute is in the Reset Button state

    A localized string of the word "reset".

    input whose type attribute is in the Image Button state

    The title attribute. Otherwise, a localized string of the phrase "Submit Query".

    summary

    The word "Details".

    Other elements

    The title attribute.

  • When the distance between two consecutive elements in the tab sequence is significant, avoid movement that would be perceived as backward. For example, on a page with a left to right language, a jump from the last element in the bottom right of the main content to the top element in a left-hand sidebar is likely to be less predictable and more difficult to follow, especially for users with a narrow field of view.

  • Follow consistent patterns across a site. The keyboard experience is more predictable when similar pages have similar focus movement patterns.

  • Do not set initial focus when the page loads except in cases where:

    • The page offers a single, primary function that nearly all users employ immediately after page load.

    • Any given user is likely to use the page often.

  • Similarly, if presentation is applied to a table element, the descendant caption, thead, tbody, tfoot, tr, th, and td elements inherit role presentation and are thus not exposed to assistive technologies. But, elements inside of the th and td elements, including nested tables, are exposed to assistive technologies.

    Radio Group with Active Descendant Example: Removed unused keycodes and fixed linting issues.

    Legacy Combobox examples: Fixed enter key documentation, made Up Arrow set activedescendant to last match in list, corrected up arrow documentation for the textbox.

  • Dialog Examples: Fixed IE11 incompatibility in role validity check.

  • Single-select listbox example: Fix a bug with aria-selected.

  • Grid Examples: Fixed how event listeners were cleaned up.

  • Menubar Examples: Applied role="none" where missing from elements in menubar and changed visual design to better distinguish between menuitemradio and meuitemcheckbox elements.

  • Radio Group Example: Fixed documentation of elements used in attributes table.

  • Spin Button Pattern: Added aria-invalid guidance for values outside allowed range.

  • Toolbar Pattern: Clarified keyboard guidance for Tab and Shift+Tab and revised description to remove guidance that conflicted with updated radio group pattern.

  • ARIA 1.0 Combobox example, including escape key behavior
  • ARIA 1.1 Combobox example labeling

  • Modal dialog example, primarily code refactoring when adding alert dialog

  • Grid pattern, including guidance on column selection

  • Listbox examples

  • Menu pattern, including guidance on menu/submenu structure

  • editor menubar example, including significant improvement to mouse behaviors.

  • Menu button example bug fixes

  • Navigation menubar example bug fixes

  • Radio group example styling

  • Tabs pattern, including labeling guidance

  • Tree view pattern, including multi-select guidance

  • Tree view example bug fixes

  • Related Issues listed in the Github project for the example

  • Design Pattern section that applies to the example

  • Accordion example
  • Mixed checkbox examples

  • Scrollable layout grid example

  • Listbox examples

  • Modal dialog example

  • editor menubar example

  • Navigation menubar example

  • Menu button examples

  • Spinbutton pattern

  • Toolbar example

  • Tree view examples

  • Breadcrumb example
  • Button example

  • Checkbox examples

  • Dialog (modal) design pattern

  • Grid examples

  • Landmark examples

  • Link example

  • Listbox example

  • Menubar examples

  • Menu button examples

  • Radio group example

  • Slider design pattern

  • Slider example

  • Example of tabs with automatic activation

  • Tree view examples

  • section

    region when it has an accessible name using aria-labelledby or aria-label

    articlearrow-up-right

    Recommended

    Cut to clipboard

    Control + X

    Command + X

    undo last action

    Control + Z

    Command + Z

    Redo action

    Control + Y

    Command + Shift + Z

    2.2 Browser and Assistive Technology Supportarrow-up-right
    2.3 Mobile and Touch Supportarrow-up-right
    3. Design Patterns and Widgetsarrow-up-right
    3.1 Accordion (Sections With Show/Hide Functionality)arrow-up-right
    3.2 Alertarrow-up-right
    4. Landmark Regionsarrow-up-right
    4.1 HTML Sectioning Elementsarrow-up-right
    4.2 General Principles of Landmark Designarrow-up-right
    5. Providing Accessible Names and Descriptionsarrow-up-right
    5.1 What ARE Accessible Names and Descriptions?arrow-up-right
    5.2 How Are Name and Description Strings Derived?arrow-up-right
    6. Developing a Keyboard Interfacearrow-up-right
    6.1 Fundamental Keyboard Navigation Conventionsarrow-up-right
    6.2 Discernible and Predictable Keyboard Focusarrow-up-right
    7. Grid and Table Propertiesarrow-up-right
    7.1 Using aria-rowcount and aria-rowindexarrow-up-right
    7.2 Using aria-colcount and aria-colindexarrow-up-right
    8. Intentionally Hiding Semantics with the presentation Rolearrow-up-right
    8.1 Effects of Role presentationarrow-up-right
    8.2 Conditions That Cause Role presentation to be Ignoredarrow-up-right
    9. Roles That Automatically Hide Semantics by Making Their Descendants Presentationalarrow-up-right
    A. Indexesarrow-up-right
    B. Change Historyarrow-up-right
    B.1 Changes in July 2019 Publication of Note Release 4arrow-up-right
    B.2 Changes in January 2019 Publication of Note Release 3arrow-up-right
    B.3 Changes in July 2018 Publication of Note Release 2arrow-up-right
    B.4 Changes in December 2017 Publication as Notearrow-up-right
    B.5 Changes in June 2017 Working Draftarrow-up-right
    C. Acknowledgementsarrow-up-right
    C.1 Major Contributors to Version 1.1arrow-up-right
    C.2 Participants active in the ARIA Authoring Practices Task Forcearrow-up-right
    C.3 Other commenters and contributors to Version 1.1arrow-up-right
    C.4 Enabling fundersarrow-up-right
    D. Referencesarrow-up-right
    D.1 Informative referencesarrow-up-right
    aria-expandedarrow-up-right
    aria-expandedarrow-up-right
    aria-controlsarrow-up-right
    aria-disabledarrow-up-right
    regionarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-describedbyarrow-up-right
    dialog patternarrow-up-right
    aria-describedbyarrow-up-right
    aria-disabledarrow-up-right
    aria-pressedarrow-up-right
    Tabs Pattern.arrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    button patternarrow-up-right
    aria-labelarrow-up-right
    grouparrow-up-right
    aria-roledescriptionarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-atomicarrow-up-right
    aria-livearrow-up-right
    aria-labelarrow-up-right
    tabs patternarrow-up-right
    aria-labelledbyarrow-up-right
    aria-disabledarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-checkedarrow-up-right
    aria-checkedarrow-up-right
    aria-checkedarrow-up-right
    grouparrow-up-right
    aria-labelledbyarrow-up-right
    aria-describedbyarrow-up-right
    ARIA 1.0 Combobox with List Autocompletearrow-up-right
    ARIA 1.0 Combobox Without Autocompletearrow-up-right
    modal dialog pattern.arrow-up-right
    textboxarrow-up-right
    searchboxarrow-up-right
    listboxarrow-up-right
    treearrow-up-right
    gridarrow-up-right
    dialogarrow-up-right
    aria-haspopuparrow-up-right
    aria-controlsarrow-up-right
    comboboxarrow-up-right
    aria-ownsarrow-up-right
    listboxarrow-up-right
    aria-multilinearrow-up-right
    aria-expandedarrow-up-right
    aria-activedescendantarrow-up-right
    aria-selectedarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-autocompletearrow-up-right
    Grid Roles, States, and Propertiesarrow-up-right
    Tree Roles, States, and Propertiesarrow-up-right
    Dialog Roles, States, and Propertiesarrow-up-right
    Alert Dialog Patternarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-describedbyarrow-up-right
    aria-hiddenarrow-up-right
    aria-controlsarrow-up-right
    articlearrow-up-right
    aria-labelledbyarrow-up-right
    aria-describedbyarrow-up-right
    aria-posinsetarrow-up-right
    aria-setsizearrow-up-right
    aria-busyarrow-up-right
    manages focus movement inside itarrow-up-right
    textboxarrow-up-right
    textboxarrow-up-right
    columnheaderarrow-up-right
    rowheaderarrow-up-right
    gridcellarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-describedbyarrow-up-right
    aria-sortarrow-up-right
    grid and table propertiesarrow-up-right
    aria-selectedarrow-up-right
    aria-readonlyarrow-up-right
    grid and table propertiesarrow-up-right
    aria-colcountarrow-up-right
    aria-rowcountarrow-up-right
    aria-colindexarrow-up-right
    aria-rowindexarrow-up-right
    aria-rowspanarrow-up-right
    aria-colspanarrow-up-right
    grid and table propertiesarrow-up-right
    aria-ownsarrow-up-right
    Example Listboxes with Rearrangeable Optionsarrow-up-right
    Deciding When to Make Selection Automatically Follow Focusarrow-up-right
    aria-labelledbyarrow-up-right
    aria-selectedarrow-up-right
    aria-multiselectablearrow-up-right
    aria-selectedarrow-up-right
    aria-setsizearrow-up-right
    aria-posinsetarrow-up-right
    aria-orientationarrow-up-right
    aria-checkedarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-describedbyarrow-up-right
    aria-valuemaxarrow-up-right
    aria-valuetextarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-orientationarrow-up-right
    aria-valuemaxarrow-up-right
    aria-valueminarrow-up-right
    aria-valuemaxarrow-up-right
    aria-valuetextarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-orientationarrow-up-right
    aria-valuemaxarrow-up-right
    aria-valuetextarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-invalidarrow-up-right
    columnheaderarrow-up-right
    rowheaderarrow-up-right
    cellarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-describedbyarrow-up-right
    aria-sortarrow-up-right
    grid and table propertiesarrow-up-right
    grid and table propertiesarrow-up-right
    aria-colcountarrow-up-right
    aria-rowcountarrow-up-right
    aria-colindexarrow-up-right
    aria-rowindexarrow-up-right
    aria-rowspanarrow-up-right
    aria-colspanarrow-up-right
    grid and table propertiesarrow-up-right
    tabpanelarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-controlsarrow-up-right
    aria-selectedarrow-up-right
    aria-labelledbyarrow-up-right
    aria-haspopuparrow-up-right
    aria-orientationarrow-up-right
    § 6.7 Focusability of disabled controlsarrow-up-right
    Navigation Treeview Example Using Computed Propertiesarrow-up-right
    Navigation Treeview Example Using Declared Propertiesarrow-up-right
    Deciding When to Make Selection Automatically Follow Focusarrow-up-right
    grouparrow-up-right
    grouparrow-up-right
    aria-expandedarrow-up-right
    aria-multiselectablearrow-up-right
    aria-selectedarrow-up-right
    aria-selectedarrow-up-right
    aria-selectedarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-levelarrow-up-right
    aria-setsizearrow-up-right
    aria-posinsetarrow-up-right
    aria-orientationarrow-up-right
    columnheaderarrow-up-right
    rowheaderarrow-up-right
    gridcellarrow-up-right
    aria-expandedarrow-up-right
    aria-multiselectablearrow-up-right
    aria-selectedarrow-up-right
    aria-selectedarrow-up-right
    aria-selectedarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-describedbyarrow-up-right
    aria-sortarrow-up-right
    grid and table propertiesarrow-up-right
    aria-readonlyarrow-up-right
    grid and table propertiesarrow-up-right
    aria-colcountarrow-up-right
    aria-rowcountarrow-up-right
    aria-colindexarrow-up-right
    aria-rowindexarrow-up-right
    aria-rowspanarrow-up-right
    aria-colspanarrow-up-right
    grid and table propertiesarrow-up-right
    aria-ownsarrow-up-right
    aria-valuemaxarrow-up-right
    aria-labelledbyarrow-up-right
    aria-labelarrow-up-right
    aria-controlsarrow-up-right
    Step 3arrow-up-right
    Step 3arrow-up-right
    Step 3arrow-up-right
    Step 3arrow-up-right
    Accessible Name Guidance by Rolearrow-up-right
    naming from child contentarrow-up-right
    Complementary Landmarkarrow-up-right
    Listbox Design Patternarrow-up-right
    Main Landmarkarrow-up-right
    Menu or Menu bar Design Patternarrow-up-right
    Menu or Menu bar Design Patternarrow-up-right
    Radio Group Design Patternarrow-up-right
    Slider Design Patternarrow-up-right
    Slider (Multi-Thumb) Design Patternarrow-up-right
    Spinbutton Design Patternarrow-up-right
    Table Design Patternarrow-up-right
    Tabs Design Patternarrow-up-right
    Toolbar Patternarrow-up-right
    Treegrid Design Patternarrow-up-right
    Tree View Design Patternarrow-up-right
    Menu or menu bararrow-up-right
    Radiogrouparrow-up-right
    Tabsarrow-up-right
    Toolbararrow-up-right
    Tree Viewarrow-up-right
    § 6.3 Focus VS Selection and the Perception of Dual Focusarrow-up-right
    aria-ownsarrow-up-right
    Menu or menu bararrow-up-right
    Tabsarrow-up-right
    Tree Viewarrow-up-right
    alertarrow-up-right
    alertdialogarrow-up-right
    applicationarrow-up-right

    useStatearrow-up-right

  • useEffectarrow-up-right

  • useContextarrow-up-right

  • Additional Hooksarrow-up-right

    • useReducerarrow-up-right

    • useCallbackarrow-up-right

  • hashtag
    Basic Hooks

    hashtag
    useState

    Returns a stateful value, and a function to update it.

    During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).

    The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.

    During subsequent re-renders, the first value returned by useState will always be the most recent state after applying updates.

    Note

    React guarantees that setState function identity is stable and won't change on re-renders. This is why it's safe to omit from the useEffect or useCallback dependency list.

    Functional updates

    If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value. Here's an example of a counter component that uses both forms of setState:

    The "+" and "-" buttons use the functional form, because the updated value is based on the previous value. But the "Reset" button uses the normal form, because it always sets the count back to the initial value.

    If your update function returns the exact same value as the current state, the subsequent rerender will be skipped completely.

    Note

    Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

    Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.

    Lazy initial state

    The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded. If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:

    Bailing out of a state update

    If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithmarrow-up-right.)

    Note that React may still need to render that specific component again before bailing out. That shouldn't be a concern because React won't unnecessarily go "deeper" into the tree. If you're doing expensive calculations while rendering, you can optimize them with useMemo.

    hashtag
    useEffect

    Accepts a function that contains imperative, possibly effectful code.

    Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React's render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.

    Instead, use useEffect. The function passed to useEffect will run after the render is committed to the screen. Think of effects as an escape hatch from React's purely functional world into the imperative world.

    By default, effects run after every completed render, but you can choose to fire them only when certain values have changedarrow-up-right.

    Cleaning up an effect

    Often, effects create resources that need to be cleaned up before the component leaves the screen, such as a subscription or timer ID. To do this, the function passed to useEffect may return a clean-up function. For example, to create a subscription:

    The clean-up function runs before the component is removed from the UI to prevent memory leaks. Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect. In our example, this means a new subscription is created on every update. To avoid firing an effect on every update, refer to the next section.

    Timing of effects

    Unlike componentDidMount and componentDidUpdate, the function passed to useEffect fires after layout and paint, during a deferred event. This makes it suitable for the many common side effects, like setting up subscriptions and event handlers, because most types of work shouldn't block the browser from updating the screen.

    However, not all effects can be deferred. For example, a DOM mutation that is visible to the user must fire synchronously before the next paint so that the user does not perceive a visual inconsistency. (The distinction is conceptually similar to passive versus active event listeners.) For these types of effects, React provides one additional Hook called useLayoutEffectarrow-up-right. It has the same signature as useEffect, and only differs in when it is fired.

    Although useEffect is deferred until after the browser has painted, it's guaranteed to fire before any new renders. React will always flush a previous render's effects before starting a new update.

    Conditionally firing an effect

    The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes.

    However, this may be overkill in some cases, like the subscription example from the previous section. We don't need to create a new subscription on every update, only if the source prop has changed.

    To implement this, pass a second argument to useEffect that is the array of values that the effect depends on. Our updated example now looks like this:

    Now the subscription will only be recreated when props.source changes.

    Note

    If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Learn more about how to deal with functions and what to do when the array values change too often.

    If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn't depend on any values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the dependencies array always works.

    If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don't forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.

    We recommend using the rule as part of our package. It warns when dependencies are specified incorrectly and suggests a fix.

    The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that's what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.

    hashtag
    useContext

    Accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.

    When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.

    Don't forget that the argument to useContext must be the context object itself:

    • Correct: useContext(MyContext)

    • Incorrect: useContext(MyContext.Consumer)

    • Incorrect: useContext(MyContext.Provider)

    A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoizationarrow-up-right.

    Tip

    If you're familiar with the context API before Hooks, useContext(MyContext) is equivalent to static contextType = MyContext in a class, or to <MyContext.Consumer>.

    useContext(MyContext) only lets you read the context and subscribe to its changes. You still need a <MyContext.Provider> above in the tree to provide the value for this context.

    Putting it together with Context.Provider

    This example is modified for hooks from a previous example in the Context Advanced Guide, where you can find more information about when and how to use Context.

    hashtag
    Additional Hooks

    The following Hooks are either variants of the basic ones from the previous section, or only needed for specific edge cases. Don't stress about learning them up front.

    hashtag
    useReducer

    An alternative to useStatearrow-up-right. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you're familiar with Redux, you already know how this works.)

    useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

    Here's the counter example from the useStatearrow-up-right section, rewritten to use a reducer:

    Note

    React guarantees that dispatch function identity is stable and won't change on re-renders. This is why it's safe to omit from the useEffect or useCallback dependency list.

    Specifying the initial state

    There are two different ways to initialize useReducer state. You may choose either one depending on the use case. The simplest way is to pass the initial state as a second argument:

    Note

    React doesn’t use the state = initialState argument convention popularized by Redux. The initial value sometimes needs to depend on props and so is specified from the Hook call instead. If you feel strongly about this, you can call useReducer(reducer, undefined, reducer) to emulate the Redux behavior, but it's not encouraged.

    Lazy initialization

    You can also create the initial state lazily. To do this, you can pass an init function as the third argument. The initial state will be set to init(initialArg).

    It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:

    Bailing out of a dispatch

    If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithmarrow-up-right.)

    Note that React may still need to render that specific component again before bailing out. That shouldn't be a concern because React won't unnecessarily go "deeper" into the tree. If you're doing expensive calculations while rendering, you can optimize them with useMemo.

    hashtag
    useCallback

    Returns a memoizedarrow-up-right callback.

    Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

    useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

    Note

    The array of dependencies is not passed as arguments to the callback. Conceptually, though, that's what they represent: every value referenced inside the callback should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.

    We recommend using the exhaustive-depsarrow-up-right rule as part of our eslint-plugin-react-hooksarrow-up-right package. It warns when dependencies are specified incorrectly and suggests a fix.

    hashtag
    useMemo

    Returns a memoizedarrow-up-right value.

    Pass a "create" function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

    Remember that the function passed to useMemo runs during rendering. Don't do anything there that you wouldn't normally do while rendering. For example, side effects belong in useEffect, not useMemo.

    If no array is provided, a new value will be computed on every render.

    You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to "forget" some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.

    Note

    The array of dependencies is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.

    We recommend using the exhaustive-depsarrow-up-right rule as part of our eslint-plugin-react-hooksarrow-up-right package. It warns when dependencies are specified incorrectly and suggests a fix.

    hashtag
    useRef

    useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

    A common use case is to access a child imperatively:

    Essentially, useRef is like a "box" that can hold a mutable value in its .current property.

    You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.

    However, useRef() is useful for more than the ref attribute. It's handy for keeping any mutable value around similar to how you'd use instance fields in classes.

    This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.

    Keep in mind that useRef doesn't notify you when its content changes. Mutating the .current property doesn't cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

    hashtag
    useImperativeHandle

    useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef:

    In this example, a parent component that renders <FancyInput ref={inputRef} /> would be able to call inputRef.current.focus().

    hashtag
    useLayoutEffect

    The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.

    Prefer the standard useEffect when possible to avoid blocking visual updates.

    Tip

    If you're migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate. However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

    If you use server rendering, keep in mind that neither useLayoutEffect nor useEffect can run until the JavaScript is downloaded. This is why React warns when a server-rendered component contains useLayoutEffect. To fix this, either move that logic to useEffect (if it isn't necessary for the first render), or delay showing that component until after the client renders (if the HTML looks broken until useLayoutEffect runs).

    To exclude a component that needs layout effects from the server-rendered HTML, render it conditionally with showChild && <Child /> and defer showing it with useEffect(() => { setShowChild(true); }, []). This way, the UI doesn't appear broken before hydration.

    hashtag
    useDebugValue

    useDebugValue can be used to display a label for custom hooks in React DevTools.

    For example, consider the useFriendStatus custom Hook described in "Building Your Own Hooks":

    Tip

    We don't recommend adding debug values to every custom Hook. It's most valuable for custom Hooks that are part of shared libraries.

    Defer formatting debug values

    In some cases formatting a value for display might be an expensive operation. It's also unnecessary unless a Hook is actually inspected.

    For this reason useDebugValue accepts a formatting function as an optional second parameter. This function is only called if the Hooks are inspected. It receives the debug value as a parameter and should return a formatted display value.

    For example a custom Hook that returned a Date value could avoid calling the toDateString function unnecessarily by passing the following formatter:

    Basic Hooksarrow-up-right
    stand-alone application you can install with NPM or Yarnarrow-up-right

    Getting Started

    This page is an overview of the React documentation and related resources.

    React is a JavaScript library for building user interfaces. Learn what React is all about on our homepage or in the tutorial.


    • Try Reactarrow-up-right

    hashtag
    Try React

    React has been designed from the start for gradual adoption, and you can use as little or as much React as you need. Whether you want to get a taste of React, add some interactivity to a simple HTML page, or start a complex React-powered app, the links in this section will help you get started.

    hashtag
    Online Playgrounds

    If you're interested in playing around with React, you can use an online code playground. Try a Hello World template on , , or .

    If you prefer to use your own text editor, you can also , edit it, and open it from the local filesystem in your browser. It does a slow runtime code transformation, so we'd only recommend using this for simple demos.

    hashtag
    Add React to a Website

    You can add React to an HTML page in one minute. You can then either gradually expand its presence, or keep it contained to a few dynamic widgets.

    hashtag
    Create a New React App

    When starting a React project, a simple HTML page with script tags might still be the best option. It only takes a minute to set up!

    As your application grows, you might want to consider a more integrated setup. There are several JavaScript toolchains we recommend for larger applications. Each of them can work with little to no configuration and lets you take full advantage of the rich React ecosystem. Learn how.

    hashtag
    Learn React

    People come to React from different backgrounds and with different learning styles. Whether you prefer a more theoretical or a practical approach, we hope you'll find this section helpful.

    • If you prefer to learn by doing, start with our practical tutorial.

    • If you prefer to learn concepts step by step, start with our guide to main concepts.

    Like any unfamiliar technology, React does have a learning curve. With practice and some patience, you will get the hang of it.

    hashtag
    First Examples

    The React homepage contains a few small React examples with a live editor. Even if you don't know anything about React yet, try changing their code and see how it affects the result.

    hashtag
    React for Beginners

    If you feel that the React documentation goes at a faster pace than you're comfortable with, check out . It introduces the most important React concepts in a detailed, beginner-friendly way. Once you're done, give the documentation another try!

    hashtag
    React for Designers

    If you're coming from a design background, are a great place to get started.

    hashtag
    JavaScript Resources

    The React documentation assumes some familiarity with programming in the JavaScript language. You don't have to be an expert, but it's harder to learn both React and JavaScript at the same time.

    We recommend going through to check your knowledge level. It will take you between 30 minutes and an hour but you will feel more confident learning React.

    Tip

    Whenever you get confused by something in JavaScript, and are great websites to check. There are also community support forums where you can ask for help.

    hashtag
    Practical Tutorial

    If you prefer to learn by doing, check out our practical tutorial. In this tutorial, we build a tic-tac-toe game in React. You might be tempted to skip it because you're not into building games -- but give it a chance. The techniques you'll learn in the tutorial are fundamental to building any React apps, and mastering it will give you a much deeper understanding.

    hashtag
    Step-by-Step Guide

    If you prefer to learn concepts step by step, our guide to main concepts is the best place to start. Every next chapter in it builds on the knowledge introduced in the previous chapters so you won't miss anything as you go along.

    hashtag
    Thinking in React

    Many React users credit reading Thinking in React as the moment React finally "clicked" for them. It's probably the oldest React walkthrough but it's still just as relevant.

    hashtag
    Recommended Courses

    Sometimes people find third-party books and video courses more helpful than the official documentation. We maintain a list of commonly recommended resources, some of which are free.

    hashtag
    Advanced Concepts

    Once you're comfortable with the main concepts and played with React a little bit, you might be interested in more advanced topics. This section will introduce you to the powerful, but less commonly used React features like context and refs.

    hashtag
    API Reference

    This documentation section is useful when you want to learn more details about a particular React API. For example, React.Component API reference can provide you with details on how setState() works, and what different lifecycle methods are useful for.

    hashtag
    Glossary and FAQ

    The glossary contains an overview of the most common terms you'll see in the React documentation. There is also a FAQ section dedicated to short questions and answers about common topics, including making AJAX requests, component state, and file structure.

    hashtag
    Staying Informed

    The React blog is the official source for the updates from the React team. Anything important, including release notes or deprecation notices, will be posted there first.

    You can also follow the on Twitter, but you won't miss anything essential if you only read the blog.

    Not every React release deserves its own blog post, but you can find a detailed changelog for every release in the , as well as on the page.

    hashtag
    Versioned Documentation

    This documentation always reflects the latest stable version of React. Since React 16, you can find older versions of the documentation on a separate page. Note that documentation for past versions is snapshotted at the time of the release, and isn't being continuously updated.

    hashtag
    Something Missing?

    If something is missing in the documentation or if you found some part confusing, please with your suggestions for improvement, or tweet at the . We love hearing from you!

    DNT-2860 Go Green Calculator

    chevron-rightNew Featureshashtag

    New Simple Calculator Input Warning And Block Explanation**

    The need for this ticket derives from a fictional unit of energy duke created called a block which is "100 kWh of 'green' energy". Because a block requires whole-number outputs that necessitates a minimum input value; which arguably needs to be explained to the user, and if the user fails to heed the instructions... have the warning change color to red if they input a lower number.

    Simple Calc Brancharrow-up-right

    i.e. input modulo 100 should return a number not equal to input.

    Additionally the calculator should include an explanation of the fictional unit being used in the output

    i.e.

    When you participate in the GoGreen Ohio program, you’re supporting the advancement of green power sources. Green power is electricity produced from natural resources – like the sun, wind, and water – that do not emit pollutants into the atmosphere.

    As a GoGreen Ohio participant, you can match your home’s electricity use by purchasing blocks* of renewable energy certificates (RECs). These RECs certify the generation of green power on your behalf. Every block you purchase supports the advancement of environmentally friendly, renewable energy sources, thereby reducing dependence on fossil fuels to generate energy.

    *One GoGreen Ohio program block represents 100 kWh of clean energy. A 10-block purchase (1,000 kWh) equals one REC (1MWh).

    This will be a replacement of the existing calculators: Go Green: (Ohio jurisdiction, calc is under the Go Green Ohio video)

    Renewable Advantage: (NC jurisdiction, look for "Calculate Your Estimated Cost"

      • Design questions contact: \

    This will be a replacement of the existing calculators:

    Go Green: (Ohio jurisdiction, calc is under the Go Green Ohio video)

    Renewable Advantage: (NC jurisdiction, look for "Calculate Your Estimated Cost"

    Design questions contact: Marcus Wilson

    SimpleCalc-Min-Val
    Go-Green(OH)
    Renewable-Advantage(NK)
    Gitbook Action Build

    Throtteling Event Listeners

    chevron-rightThrottlehashtag

    Using Throttling and Debouncing with React hooks - DEV Community

    Excerpt

    Throttling and debouncing techniques has been in use for past many years in javascript. In this post I'd like to share my knowledge on how we can use throttle and debounce functions with help of react hooks.

    Consider below example with two routes

    DNT-2657 Lists must be contained within semantically correct containers

    src/components/MultiStepForm/components/ConfirmationStep/index.tsx

    Description

    Notes:

    The information about call-time preference is visually displayed as a description list. In this case, it's a question and then an answer. This content is not marked up as a list.

    When content is presented as a list, or a clearly related collection of items, marking it up using semantic list HTML elements helps assistive technology users to navigate and orient themselves within a page of content. Screen reader users can navigate to (or over) lists when they are correctly marked up. Also, when reading content in a list, assistive technology provides rich information about how long the list is and where the user is in the list. For example: "List, 9 items, Home, 1 of 9, About, 2 of 9".

    This particular list is confusing because it is displayed even when there is no time indicated, like when a user chooses email as the preferred method of communication.

    DNT-2785

    /info/unindexed/dpa-conversion-business

    The Image Slideshow component seems to have shrunk after JSS conversion. See it here: , compared to pre-JSS: . As you can see, it cuts off some of the image space and the description underneath is completely gone.

    chevron-rightModal.tsxhashtag

    DNT-2930 Hero Component

    hashtag
    Hero

    Features a full-width image (light or dark) with superimposed text (light or dark contrasting). See a with a site header.

    hashtag

    DNT-2659 A meaningful image must have a text alternative that serves the equivalent purpose

    chevron-rightInfohashtag

    2654-a11y-Audit-Form-Errors

    Required and invalid form controls must convey/expose the fact that they are required and invalid

    {% embed url="https: //scjsstest.duke-energy.com/home-services/gas-line-repair/enroll" %}

    [![BSowa](https: //jiraprod.duke-energy.com/secure/useravatar?size=small&ownerId=bsowa&avatarId=12234)Sowa, Bill](https: //jiraprod.duke-energy.com/secure/ViewProfile.jspa?name=BSowa) created issue - Feb/10/22 8:20 AM[![BSowa](https: //jiraprod.duke-energy.com/secure/useravatar?size=xsmall&ownerId=bsowa&avatarId=12234)Sowa, Bill](https: //jiraprod.duke-energy.com/secure/ViewProfile.jspa?name=BSowa) made changes - Feb/10/22 8:33 AM

    Initial Notes
    Updated
    const [state, setState] = useState({});
    setState((prevState) => {
      // Object.assign would also work
      return { ...prevState, ...updatedValues };
    });
    const [state, setState] = useState(initialState);
    setState(newState);
    function Counter({ initialCount }) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
          <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
        </>
      );
    }
    const [state, setState] = useState(() => {
      const initialState = someExpensiveComputation(props);
      return initialState;
    });
    useEffect(didUpdate);
    useEffect(() => {
      const subscription = props.source.subscribe();
      return () => {
        // Clean up the subscription
        subscription.unsubscribe();
      };
    });
    useEffect(() => {
      const subscription = props.source.subscribe();
      return () => {
        subscription.unsubscribe();
      };
    }, [props.source]);
    const value = useContext(MyContext);
    const themes = {
      light: {
        foreground: "#000000",
        background: "#eeeeee"
      },
      dark: {
        foreground: "#ffffff",
        background: "#222222"
      }
    };
    
    const ThemeContext = React.createContext(themes.light);
    
    function App() {
      return (
        <ThemeContext.Provider value={themes.dark}>
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    
    function Toolbar(props) {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    
    function ThemedButton() {
      const theme = useContext(ThemeContext);
    
      return (
        <button style={{ background: theme.background, color: theme.foreground }}>
          I am styled by theme context!
        </button>
      );
    }
    const [state, dispatch] = useReducer(reducer, initialArg, init);
    const initialState = { count: 0 };
    
    function reducer(state, action) {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
        case "decrement":
          return { count: state.count - 1 };
        default:
          throw new Error();
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: "decrement" })}>-</button>
          <button onClick={() => dispatch({ type: "increment" })}>+</button>
        </>
      );
    }
      const [state, dispatch] = useReducer(
        reducer,
        {count: initialCount}
      );
    function init(initialCount) {
      return {count: initialCount};
    }
    
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return {count: state.count + 1};
        case 'decrement':
          return {count: state.count - 1};
        case 'reset':
          return init(action.payload);
        default:
          throw new Error();
      }
    }
    
    function Counter({initialCount}) {
      const [state, dispatch] = useReducer(reducer, initialCount, init);
      return (
        <>
          Count: {state.count}
          <button
            onClick={() => dispatch({type: 'reset', payload: initialCount})}>
            Reset
          </button>
          <button onClick={() => dispatch({type: 'decrement'})}>-</button>
          <button onClick={() => dispatch({type: 'increment'})}>+</button>
        </>
      );
    }
    const memoizedCallback = useCallback(() => {
      doSomething(a, b);
    }, [a, b]);
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    const refContainer = useRef(initialValue);
    function TextInputWithFocusButton() {
      const inputEl = useRef(null);
      const onButtonClick = () => {
        // `current` points to the mounted text input element
        inputEl.current.focus();
      };
      return (
        <>
          <input ref={inputEl} type="text" />
          <button onClick={onButtonClick}>Focus the input</button>
        </>
      );
    }
    useImperativeHandle(ref, createHandle, [deps]);
    function FancyInput(props, ref) {
      const inputRef = useRef();
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
      return <input ref={inputRef} ... />;
    }
    FancyInput = forwardRef(FancyInput);
    useDebugValue(value);
    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      // ...
    
      // Show a label in DevTools next to this Hook
      // e.g. "FriendStatus: Online"
      useDebugValue(isOnline ? 'Online' : 'Offline');
    
      return isOnline;
    }
    useDebugValue(date, (date) => date.toDateString());
    5.3.2.3 Naming with Referenced Content Via aria-labelledbyarrow-up-right
  • 5.3.2.4 Naming Form Controls with the Label Elementarrow-up-right

  • 5.3.2.5 Naming Fieldsets with the Legend Elementarrow-up-right

  • 5.3.2.6 Naming Tables and Figures with Captionsarrow-up-right

  • 5.3.2.7 Fallback Names Derived from Titles and Placeholdersarrow-up-right

  • 6.9.2.3 Browser Key Conflictsarrow-up-right
  • 6.9.2.4 Intentional Key Conflictsarrow-up-right

  • 4.3.4 Formarrow-up-right
    4.3.5 Mainarrow-up-right
    4.3.6 Navigationarrow-up-right
    4.3.7 Regionarrow-up-right
    4.3.8 Searcharrow-up-right
    5.3.1.3 Rule 3: Prefer Native Techniquesarrow-up-right
    5.3.1.4 Rule 4: Avoid Browser Fallbackarrow-up-right
    5.3.1.5 Rule 5: Compose Brief, Useful Namesarrow-up-right
    5.3.2 Naming Techniquesarrow-up-right
    5.3.2.1 Naming with Child Contentarrow-up-right
    5.3.2.2 Naming with a String Attribute Via aria-labelarrow-up-right
    5.3.3 Composing Effective and User-friendly Accessible Namesarrow-up-right
    5.3.4 Accessible Name Guidance by Rolearrow-up-right
    5.3.5 Accessible name calculationarrow-up-right
    5.3.5.1 Examples of non-recursive accessible name calculationarrow-up-right
    5.3.5.2 Examples of recursive accessible name calculationarrow-up-right
    5.4.1.2 Describing Tables and Figures with Captionsarrow-up-right
    5.4.1.3 Descriptions Derived from Titlesarrow-up-right
    5.4.2 Accessible description calculationarrow-up-right
    6.9.1.2 Choose Appropriate Shortcut Behaviorarrow-up-right
    6.9.1.3 Choose Where to Add Shortcutsarrow-up-right
    6.9.2 Assigning Keyboard Shortcutsarrow-up-right
    6.9.2.1 Operating System Key Conflictsarrow-up-right
    6.9.2.2 Assistive Technology Key Conflictsarrow-up-right
    useMemoarrow-up-right
    useRefarrow-up-right
    useImperativeHandlearrow-up-right
    useLayoutEffectarrow-up-right
    useDebugValuearrow-up-right
    exhaustive-depsarrow-up-right
    eslint-plugin-react-hooksarrow-up-right
    Learn Reactarrow-up-right
    Staying Informedarrow-up-right
    Versioned Documentationarrow-up-right
    Something Missing?arrow-up-right
    CodePenarrow-up-right
    CodeSandboxarrow-up-right
    Stackblitzarrow-up-right
    download this HTML filearrow-up-right
    this overview of React by Tania Rasciaarrow-up-right
    these resourcesarrow-up-right
    this JavaScript overviewarrow-up-right
    MDNarrow-up-right
    javascript.infoarrow-up-right
    @reactjs accountarrow-up-right
    CHANGELOG.md file in the React repositoryarrow-up-right
    Releasesarrow-up-right
    file an issue for the documentation repositoryarrow-up-right
    @reactjs accountarrow-up-right
    import React, { useRef, PropsWithChildren } from 'react';
    import { createPortal } from 'react-dom';
    import { a11yAction } from 'src/lib/helpers';
    import { Placeholder } from '@sitecore-jss/sitecore-jss-react';
    import SvgLoader from 'src/components/SvgLoader';
    import Transition from 'src/lib/Transition';
    import { useBodyContext } from 'src/components/ContentWrapper/context';
    import { useExperienceEditor } from 'src/lib/useExperienceEditor';
    import useMediaQuery from 'src/lib/useMediaQuery';
    import usePreventBodyScroll from 'src/lib/usePreventBodyScroll';
    import { ModalComponentTypes, ModalTypes } from './types';
    import { useFocusTrap } from 'src/lib/useFocusTrap';
    
    const Portal = ({ children }: PropsWithChildren<{}>) => {
      if (typeof window === 'undefined') return null;
      // Create the root element that content is rendered into
      // Used for "Portal" or other functionality that querySelectors for the '#root'
      let portalRoot = document.getElementById('root');
      if (!portalRoot) {
        portalRoot = document.createElement('div');
        portalRoot.setAttribute('id', 'root');
        portalRoot = document.body.appendChild(portalRoot);
      }
    
      return createPortal(children, portalRoot);
    };
    
    const Modal = ({ id, rendering }: ModalTypes) => {
      const { dispatch, state } = useBodyContext();
      const { activeId, isOpen } = state.modal;
      const { isEEActive } = useExperienceEditor();
      const shouldDisplayModal = activeId === id && isOpen;
    
      return isEEActive ? (
        <div className="border">
          The Modal Container
          <Placeholder name="jss-public-modal-container" rendering={rendering} />
        </div>
      ) : (
        <ModalComponent
          isActive={shouldDisplayModal}
          onCloseHandler={() => dispatch({ type: 'modalClose' })}
          {...{ id }}
        >
          <Placeholder name="jss-public-modal-container" rendering={rendering} />
        </ModalComponent>
      );
    };
    
    const ModalComponent = ({
      children,
      controls,
      isActive,
      onCloseHandler = () => {},
    }: React.PropsWithChildren<ModalComponentTypes>) => {
      const modalBackgroundRef = useRef<HTMLDivElement>(null);
      const modalContainerRef = useRef<HTMLDivElement>(null);
      const isMobile = !useMediaQuery('md');
    
      // when the modal is active, do not allow the body to scroll
      usePreventBodyScroll(isActive);
    
      // classes for the modal 'background' div
      const activeClass = isActive ? 'bg-black bg-opacity-80 pointer-events-auto z-modal' : '';
    
      // classes for the modal 'container' div
      const containerBackgroundClass = controls?.isTransparent ? '' : 'border border-gray bg-white';
      const containerScrollClass = controls?.preventScroll ? '' : 'overflow-auto';
      const containerClass = `${containerBackgroundClass} ${containerScrollClass}`;
    
      // closes the modal when clicking outside of the modal
      const handleClose = (event: React.MouseEvent<HTMLDivElement>) => {
        if (
          event &&
          event.target &&
          event.target instanceof Element &&
          event.target.contains(modalBackgroundRef.current) &&
          isActive
        ) {
          onCloseHandler();
        }
      };
    
      useFocusTrap({
        shouldTrap: isActive,
        container: modalBackgroundRef.current,
        onExit: () => onCloseHandler(),
        onEnter: ([first]) => first?.focus(),
      });
    
      return (
        <Portal>
          <div
            className={`js-modal fixed h-screen w-full top-0 left-0 flex flex-col items-center p-12 pointer-events-none ${activeClass}`}
            ref={modalBackgroundRef}
            {...a11yAction(handleClose)}
            role="dialog"
            aria-hidden={!isActive}
            tabIndex={-1}
          >
            <Transition.RevealDown
              active={isActive}
              className={`relative px-24 pt-32 pb-24 m-auto w-full max-w-3xl ${containerClass}`}
            >
              <div ref={modalContainerRef}>
                <button
                  aria-label="close"
                  className="visible absolute right-0 top-0 mr-24 md:mt-16 mt-0 z-overlay"
                  onClick={onCloseHandler}
                >
                  <SvgLoader
                    aria-hidden={true}
                    focusable={false}
                    color={controls?.isTransparent ? 'text-white' : 'text-teal-dark'}
                    name="X"
                    size={isMobile ? 20 : 24}
                  />
                </button>
                {children}
              </div>
            </Transition.RevealDown>
          </div>
        </Portal>
      );
    };
    
    export { Modal as default, ModalComponent };
    https://scprod-cms.duke-energy.com/info/unindexed/dpa-conversion-businessarrow-up-right
    https://scdev28.duke-energy.com/info/unindexed/dpa-conversion-businessarrow-up-right

    Component Creation

    README

    Special Props Warning

    Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.

    For instance, attempting to access this.props.key from a component (i.e., the render function or propTypes) is not defined. If you need to access the same value within the child component, you should pass it as a different prop (ex: <ListItemWrapper key={result.id} id={result.id} />). While this may seem redundant, it's important to separate app logic from reconciling hints.

    /
    and
    /count
    rendering respective components.

    Throttling Example with useEffect

    Suppose we need to subscribe a scroll event on Count component on its mount and just increment the count on every scroll event.

    Code without using throttle or debounce techniques will be like:

    Suppose in practical applications you need to use throttle and wait for every 100ms before we execute increaseCount. I have used the lodash throttle function for this example.

    Wait, no need to hurry. It will work if you are at /count route. The increaseCount function will be throttled and will increase the count after 100ms of intervals.

    But as you move to the / route to render the Home component and unmount the Count component, and start scrolling on home page, you will notice a warning in console which warns about memory leak. This is probably because the scroll event was not cleaned properly. The reason is _.throttle(increaseCount, 100) is called again during unmount and returns another function which does not match that created during the mount stage. What if we create a variable and store the throttled instance.

    like this

    But it has problem too. The throttledCount is created on every render, which is not at all required. This function should be initiated once which is possible inside the useEffect hook. As it will now be computed only once during mount.

    Debounce Example using useCallback or useRef

    Above example is pretty simple. Let's look at another example where there is an input field and you need to increment the count only after user stops typing for certain time. And there is text which is updated on every keystroke which re renders the component on every input.

    Code with debounce:

    This will not work. The count will increase for every keystroke. The reason behind is that on every render, a new debouncedCount is created. We have to store this debounced function such that it is initiated only once like that in useEffect in above example. Here comes use of useCallback. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed - React docs Replace

    with

    and it will work. Because this time the function is evaluated only once at the initial phase.

    Or we can also use useRef by doing this

    One should always keep in mind that every render call of react functional component will lead to expiration of local variables and re-initiation unless you memoize them using hooks.

    export default function App() {
      return (
        <BrowserRouter>
          <div>
            <nav>
              <ul>
                <li>
                  <Link to="/">Home</Link>
                </li>
                <li>
                  <Link to="/count">Count</Link>
                </li>
              </ul>
            </nav>
            <Switch>
    
    function Count() {
      const [count, setCount] = useState(1);
      useEffect(() => {
        window.addEventListener('scroll', increaseCount);
        return () => window.removeEventListener('scroll', increaseCount);
      }, []);
      const increaseCount = () => {
        setCount(count => count + 1);
      }
      return <h2 style={{marginBottom: 1200}}>Count {count}</h2>;
    }
    function Count() {
      const [count, setCount] = useState(1);
      useEffect(() => {
        window.addEventListener('scroll', _.throttle(increaseCount, 100));
        return () => window.removeEventListener('scroll', _.throttle(increaseCount, 100));
      }, []);
      const increaseCount = () => {
        setCount(count => count + 1);
      }
      return <h2 style={{marginBottom: 1200}}>Count {count}</h2>;
    }
    const throttledCount = _.throttle(increaseCount, 100);
    useEffect(() => {
        window.addEventListener('scroll', throttledCount);
        return () => window.removeEventListener('scroll', throttledCount);
      }, []);
    useEffect(() => {
        const throttledCount = _.throttle(increaseCount, 100);
        window.addEventListener('scroll', throttledCount);
        return () => window.removeEventListener('scroll', throttledCount);
      }, []);
    function Count() {
      const [count, setCount] = useState(1);
      const [text, setText] = useState("");
      const increaseCount = () => {
        setCount(count => count + 1);
      }
      const debouncedCount = _.debounce(increaseCount, 1000);
      const handleChange = (e) => {
        setText(e.target.value);
        debouncedCount();
      }
      return <>
        <h2>Count {count}</h2>
        <h3>Text {text}</h3>
    
    
    const debouncedCount = _.debounce(increaseCount, 1000);
    const debouncedCount = useCallback(_.debounce(increaseCount, 1000),[]);
    const debouncedCount = useRef(debounce(increaseCount, 1000)).current;

    Recommendation

    Remove the list entirely and write this information as a sentence. "We will reach out to you by phone during business hours," for example. If the user indicates that they prefer email, either write a new sentence indicating that preference, "One of our representatives will email you shortly," or remove the sentence altogether.

    If you choose to keep the question and answer structure, use a description list (<dl>)for this data.

    Code example (simplified)

    Resources

    Using description listsarrow-up-right

    chevron-rightDescription Listshashtag

    Using description lists

    Important Information about Techniques

    See Understanding Techniques for WCAG Success Criteriaarrow-up-right for important information about the usage of these informative techniques and how they relate to the normative WCAG 2.1 success criteria. The Applicability section explains the scope of the technique, and the presence of techniques for a specific technology does not imply that the technology can be used in all situations to create content that meets WCAG 2.1.

    Applicability

    HTML and XHTML

    This technique relates to (Sufficient when used with ).

    Description

    The objective of this technique is to provide the description of names or terms by presenting them in a description list. The list is marked up using the dl element. Within the list, each term is put in a separate dt element, and its description goes in the dd element directly following it. Multiple terms can be associated with a single description, as can a single term with multiple descriptions, provided that semantic sequence is maintained. The title attribute can be used to provide additional information about the description list. Usage of description lists ensures that terms and their descriptions are semantically related even as presentation format changes, as well as ensuring that these terms and descriptions are semantically grouped as a unit.

    Description lists are easiest to use when the descriptions are ordered alphabetically. A common use for description lists is a glossary of terms.

    NOTE

    In HTML5 the name "Description list" was introduced. In previous versions these lists were referred to as "Definition lists".

    Examples

    Example 1

    A list of descriptions of nautical terms used on a Website about sailing.

    Resources

    Resources are for information purposes only, no endorsement implied.

    Related Techniques

    Tests

    Procedure

    For any set of terms and their associated descriptions:

    1. Check that the list is contained within a dl element.

    2. Check that each term in the list being described is contained within a dt element.

    3. Check that when there is more than one term that shares the same decription that the dt

    Expected Results

    • All checks above are true.

    Usage Guidance

    Do

    • Use on home pages and landing pages with multiple child pages.

    • When used on pages other than Home Pages or Section Landing Pages, the headline (H1) must be the page title.

    • Include a title/headline (required), a description (optional), and call-to-action link button(s) (optional).

    • Ensure that images clearly communicate the concept across all breakpoints and that critical information is not cropped out.

    Do Not

    • Use more than ONE Hero on a page.

    Toggle Component Options

    Theme Black to Gray-Darker Blue-Dark to Blue Blue to Teal-Darker Green-Dark to Green Blue-Dark to Green-Dark Green-Dark to Blue-Dark Gray-Light to white Gray-Light to Gray-Lighter Teal-Light to Teal-Lighter Image Business Solar Chef Family Landscape One Person Store Solar Solar Cell Two Person Store Content Content 1 Content 2 Content 3 Too Much Content Action No Actions Primary Action Primary and Secondary Action

    hashtag
    Say Yes to Renewables

    Striving for sustainability is just smart business

    START NOWarrow-up-right

    hashtag
    Accessibility

    Labeling Expectations

    • Use a unique `id=""` on the title.

    • Use a unique `id=""` on the action link button(s).

    • Use `aria-labelledby=""` on the link buttons referencing the id of the link button(s) and the id of the title.

    Keyboard Expectations

    • When you TAB into the `Hero` component, focus will go onto the link button(s).

    Resources

    • Web Accessibility: Linksarrow-up-right

    demo pagearrow-up-right
  • Projectsarrow-up-right

  • Issuesarrow-up-right

  • Boardsarrow-up-right

  • Time in Statusarrow-up-right

  • Createarrow-up-right

  • Give feedback to Atlassianarrow-up-right

  • Helparrow-up-right

  • Administrationarrow-up-right

  • arrow-up-right

  • Welcome to Duke Energy Enterprise JIRA ~ Please enter a ticket through MyIT if you have any significant problems with the tool. Thank you! Have a great day!

    arrow-up-right

    DXT Ninja Turtlesarrow-up-right

    ======================================================================================================

    DNT boardarrow-up-right

    • User story maparrow-up-right

    • Kanban boardarrow-up-right

    • Releasesarrow-up-right

    1. DXT Ninja Turtlesarrow-up-right

    2. DNT-2654 a11y audit - Formarrow-up-right

    3. DNT-2659arrow-up-right

    Details

    • Type: Sub-task

    • Status:REVIEW (View Workflowarrow-up-right)

    • Priority: Medium

    • Resolution:Unresolved

    • Labels:

      None

    Description

    (MultiStepForm issue) (Stepper component)

    Notes:

    The check mark that indicates a completed step in the form is an <svg> image without a text alternative. Blind and low vision users will not know that this is a completed step.

    Recommendation

    Use the SVG <title> element to provide a text alternative that's appropriate for the context. To ensure support in assistive technologies, provide role="img" and aria-labelledby attributes in the <svg> element.

    SVG with <title> element, role="img", and the aria-labelledby attribute (Simplified code)

    Complete ...

    Contextually Marking up accessible images and SVGsarrow-up-right

    • Options

    Attachments

    Drop files to attach, or browse.

    1. arrow-up-right

      36546.pngarrow-up-right

      5 kB

      Mar/08/22 10:54 AM

    2. arrow-up-right

      187 kB

      1 hour ago

    • Add Linkarrow-up-right

    Issue Links

    is blocked by

    DNT-2794arrow-up-right Add title support to SvgLoader

    • DONE

    Delete this linkarrow-up-right

    relates to

    DNT-2661arrow-up-right Lists must be contained within semantically correct containers

    • REVIEW

    Delete this linkarrow-up-right

    Activity

    • Allarrow-up-right

    • Comments

    • Work Logarrow-up-right

    Permalinkarrow-up-right Editarrow-up-right Deletearrow-up-right

    Guner, Bryanarrow-up-right added a comment - 1 hour ago

    Validated in test environment:

    • Commentarrow-up-right

    People

    Assignee:

    Guner, Bryan

    Reporter:

    Sowa, Bill

    Votes:

    0 arrow-up-rightVote for this issuearrow-up-right

    Watchers:

    2 arrow-up-rightStop watching this issuearrow-up-right

    Dates

    Created:

    Feb/10/22 8:42 AM

    Updated:

    18 minutes ago

    Development

    • 1 brancharrow-up-right

      Updated 6 days ago

    • 5 commitsarrow-up-right

      Latest 5 days ago

    • MERGED

      Updated 7 hours ago

    Agile

    Future Sprint:

    Sprint 14arrow-up-right

    View on Boardarrow-up-right

    Hipchat discussions

    Do you want to discuss this issue? Connect to Hipchat.

    ConnectDismissarrow-up-right

    [3/1 2:28 PM] Macias, Marcie

    home-services/gas-line-repair/enroll

    [3/1 2:42 PM] Macias, Marcie

    home/start-stop-move/stop

    Linked Applicationsarrow-up-right
    arrow-up-right
    Dashboardsarrow-up-right

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {{}} {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format </p>{code} {{}}

    hashtag
    [DXT Ninja Turtles](https:

    //jiraprod.duke-energy.com/browse/DNT) / [DNT-2654 a11y audit - Form](https: //jiraprod.duke-energy.com/browse/DNT-2654) / [DNT-2655](https: //jiraprod.duke-energy.com/browse/DNT-2655)

    Notes:

    Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid.

    Recommendation

    Set aria-invalid="true" on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the aria-invalid attribute or set it to false.

    Setting aria-invalid

    <label for="address">Address</label> <input id="address" type="text" aria-invalid="true">

    When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use aria-describedby to associate the error message with the form field. Once the field is valid, remove the error message.

    Setting aria-describedby to point to an error message

    Email <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format name@example.com

    • Options

    6cbe92fc1812489eacd2322b8e5a2e2f

    Notes: Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid. h5. Recommendation Set {{aria-invalid="true"}} on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the {{aria-invalid}} attribute or set it to {{false}}. Setting aria-invalid {{<label for="address">Address</label>}} {{<input id="address" type="text" aria-invalid="true">}} When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use {{aria-describedby}} to associate the error message with the form field. Once the field is valid, remove the error message. Setting {{aria-describedby}} to point to an error message {code:java} <label for="email">Email</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format name@example.comenvelope</p>{code}

    MatchElectricUsage

    200kWh

    250kWh

    "The minimum kWh that can be entered is"

    ChooseYourPayment

    $2.00

    $3.00

    "The minimum amount that can be entered is $"

    Abstractarrow-up-right
    https://www.duke-energy.com/home/products/renewable-energy/gogreen-energyarrow-up-right
    https://www.duke-energy.com/home/products/renewable-advantagearrow-up-right
    marcus.wilson@duke-energy.comenvelope
    https://app.abstract.com/projects/7d33aa49-f1f0-47eb-971d-893d6457bcbc/branches/4d491d6e-63f5-4bad-951e-0f327cfc047c/commits/afba0d4f418ba1a5f186815496c8331add993f74/files/56AED96A-C786-42FD-8B75-CFA17F1BE644/layers/BB077CE9-8557-4DDA-8C01-FC5575EE35A4?collectionId=123e3638-3117-4bbb-b7b0-8e43052397a8&collectionLayerId=6d234c60-58b2-4027-a631-e01d158248abarrow-up-right
    https://www.duke-energy.com/home/products/renewable-energy/gogreen-energyarrow-up-right
    https://www.duke-energy.com/home/products/renewable-advantagearrow-up-right

    Storybook

    Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

    It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

    To get it up and running navigate to the app repo and, from the command line, enter:

    Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

    hashtag
    Stories

    A story, much like a React component, is a function that describes how to render a component.

    You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

    For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

    So here's how the initial default PushDownPanel might look:

    And then we can add variations based on different states, for example a version that uses icons:

    And the new stories will show up in the sidebar navigation, like so:

    hashtag
    Args

    Story definitions can be further improved to take advantage of Storybook's "args" concept.

    A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

    Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

    hashtag
    Controls

    Controls allow designers and developers to explore component behavior by mucking about with its arguments.

    Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

    This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

    Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

    A select menu to control the number of items that render:

    And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

    hashtag
    A11y and Other Addons

    Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

    One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

    We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

    hashtag
    Anatomy of a Story

    Stories exist alongside the other component files as stories.js.

    Here's an example of how a typical story might take shape:

    Let's break this down line by line:

    Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

    Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

    Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

    It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

    Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

    We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

    In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

    Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

    We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

    And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

    hashtag
    storybookCustomArgs

    The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

    We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

    hashtag
    createStoryOptions

    Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

    Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

    hashtag
    Resources

    • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

    • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

    • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

    Unknown Prop Warning

    The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.

    There are a couple of likely reasons this warning could be appearing:

    1. Are you using {...this.props} or cloneElement(element, this.props)? Your component is transferring its own props directly to a child element (eg. transferring props). When transferring props to a child component, you should ensure that you are not accidentally forwarding props that were intended to be interpreted by the parent component.

    2. You are using a non-standard DOM attribute on a native DOM node, perhaps to represent custom data. If you are trying to attach custom data to a standard DOM element, consider using a custom data attribute as described .

    3. React does not yet recognize the attribute you specified. This will likely be fixed in a future version of React. However, React currently strips all unknown attributes, so specifying them in your React app will not cause them to be rendered.

    4. You are using a React component without an upper case. React interprets it as a DOM tag because React JSX transform uses the upper vs. lower case convention to distinguish between user-defined components and DOM tags.


    To fix this, composite components should "consume" any prop that is intended for the composite component and not intended for the child component. Example:

    Bad: Unexpected layout prop is forwarded to the div tag.

    Good: The spread syntax can be used to pull variables off props, and put the remaining props into a variable.

    Good: You can also assign the props to a new object and delete the keys that you're using from the new object. Be sure not to delete the props from the original this.props object, since that object should be considered immutable.

    Sitecore Config

    The purpose of this spreadsheet is to allow values to be changed in lower environments. After EVERY deployment, the lower environments (Test, QA, etc) have their master databases restored from Production. That is great news that most of the conten is fresh but one issue is that any values that are hard coded for production, are also hard coded for the lower environments. There needs to be a way to modify these "production" values to their lower environment equivalents. That is where this spreadsheet comes in. This spreadsheet is used against each new database restore to update the production values to their lower environmetn equivalents.

    Item - The full sitecore path to the item that needs to be modified.

    GUID - The GUID to the item that needs to be modified.

    DNT-2724 Accordion Testing

    chevron-right5 Things You Didn't Know About React Testing Libraryhashtag

    Excerpt

    I've just sold myself to the gods of click-baiting by making an "x things you didn't know about y" post. But hey, at least there no subtitle that says "number three will blow your mind!"


    I've just sold myself to the gods of click-baiting by making an "x things you didn't know about y" post. But hey, at least there no subtitle that says "number three will blow your mind!"

    Jokes aside the items in this list are concepts that I usually see beginners struggling with. At the same time, learning these concepts will vastyly improve your testing game. I know it did with mine.

    1. Everything is a DOM node

    This is usually the first misconception that beginners have when they start approaching Testing Library. It is especially true for those developers like me that came from .

    Many think that getByText and other helper methods return some special wrapper around the React component. People even ask how to implement a getByReactComponent helper.

    When you work with Testing Library you are dealing with . This is made clear by the first :

    If it relates to rendering components, then it should deal with DOM nodes rather than component instances, and it should not encourage dealing with component instances.

    If you want to check for yourself, it's as simple as this:

    Once you realize that you're dealing with DOM nodes you can start taking advantage of all the DOM APIs like or 👍

    2. debug's optional parameter

    Since we now know that we're dealing with a DOM structure, it would be helpful to be able to "see" it. This is what is meant for:

    ometimes thought debug's output can be very long and difficult to navigate. In those cases, you might want to isolate a subtree of your whole structure. You can do this easily by passing a node to debug:

    3. Restrict your queries with within

    Imagine you're testing a component that renders this structure:

    You want to test that each ID gets its correct value. You can't use getByText('Apples') because there are two nodes with that value. Even if that wasn't the case you have no guarantee that the text is in the correct row.

    What you want to do is to run getByText only inside the row you're considering at the moment. This is exactly what is for:

    4. Queries accept functions too

    You have probably seen an error like this one:

    Usually, it happens because your HTML looks like this:

    The solution is contained inside the error message: "[...] you can provide a function for your text matcher [...]".

    What's that all about? It turns out matchers accept strings, regular expressions or functions.

    The function gets called for each node you're rendering. It receives two arguments: the node's content and the node itself. All you have to do is to return true or false depending on if the node is the one you want.

    An example will clarify it:

    We're ignoring the content argument because in this case, it will either be "Hello", "world" or an empty string.

    What we are checking instead is that the current node has the right . hasText is a little helper function to do that. I declared it to keep things clean.

    That's not all though. Our div is not the only node with the text we're looking for. For example, body in this case has the same text. To avoid returning more nodes than needed we are making sure that none of the children has the same text as its parent. In this way we're making sure that the node we're returning is the smallest—in other words the one closes to the bottom of our DOM tree.

    5. You can simulate browsers events with user-event

    Ok, this one is a shameless plug since I'm the author of user-event. Still, people—myself included—find it useful. Maybe you will too.

    All user-event tries to do is to simulate the events a real user would do while interacting with your application. What does it mean? Imagine you have an input field, and in your tests, you want to enter some text in it. You would probably do something like this:

    It works but it doesn't simulate what happens in the browser. A real user would most likely move the mouse to select the input field and then start typing one character at the time. This, in turns, fires many events (blur, focus, mouseEnter, keyDown, keyUp...). user-event simulates all those events for you:

    Types VS Interfaces

    hashtag
    Object Types

    In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types.

    As we've seen, they can be anonymous:

    or they can be named by using either an interface

    or a type alias.

    In all three examples above, we've written functions that take objects that contain the property name (which must be a string) and age (which must be a number).

    hashtag
    Property Modifiers

    Each property in an object type can specify a couple of things: the type, whether the property is optional, and whether the property can be written to.

    hashtag
    Optional Properties

    Much of the time, we'll find ourselves dealing with objects that might have a property set. In those cases, we can mark those properties as optional by adding a question mark (?) to the end of their names.

    In this example, both xPos and yPos are considered optional. We can choose to provide either of them, so every call above to paintShape is valid. All optionality really says is that if the property is set, it better have a specific type.

    We can also read from those properties - but when we do under [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks), TypeScript will tell us they're potentially undefined.

    In JavaScript, even if the property has never been set, we can still access it - it's just going to give us the value undefined. We can just handle undefined specially.

    Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to support it.

    Here we used [a destructuring pattern](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) for paintShape's parameter, and provided [default values](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Default_values) for xPos and yPos. Now xPos and yPos are both definitely present within the body of paintShape, but optional for any callers to paintShape.

    Note that there is currently no way to place type annotations within destructuring patterns. This is because the following syntax already means something different in JavaScript.

    In an object destructuring pattern, shape: Shape means "grab the property shape and redefine it locally as a variable named Shape. Likewise xPos: number creates a variable named number whose value is based on the parameter's xPos.

    Using [mapping modifiers](https: //www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers), you can remove optional attributes.

    hashtag
    readonlyProperties

    Properties can also be marked as readonly for TypeScript. While it won't change any behavior at runtime, a property marked as readonly can't be written to during type-checking.

    Using the readonly modifier doesn't necessarily imply that a value is totally immutable - or in other words, that its internal contents can't be changed. It just means the property itself can't be re-written to.

    It's important to manage expectations of what readonly implies. It's useful to signal intent during development time for TypeScript on how an object should be used. TypeScript doesn't factor in whether properties on two types are readonly when checking whether those types are compatible, so readonly properties can also change via aliasing.

    Using [mapping modifiers](https: //www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers), you can remove readonly attributes.

    hashtag
    Index Signatures

    Sometimes you don't know all the names of a type's properties ahead of time, but you do know the shape of the values.

    In those cases you can use an index signature to describe the types of possible values, for example:

    Above, we have a StringArray interface which has an index signature. This index signature states that when a StringArray is indexed with a number, it will return a string.

    An index signature property type must be either ‘string' or ‘number'.

    chevron-rightIt is possible to support both types of indexers...hashtag

    While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name's type does not match the string index's type, and the type checker gives an error:

    However, properties of different types are acceptable if the index signature is a union of the property types:

    Finally, you can make index signatures readonly in order to prevent assignment to their indices:

    You can't set myArray[2] because the index signature is readonly.

    hashtag
    Extending Types

    It's pretty common to have types that might be more specific versions of other types. For example, we might have a BasicAddress type that describes the fields necessary for sending letters and packages in the U.S.

    In some situations that's enough, but addresses often have a unit number associated with them if the building at an address has multiple units. We can then describe an AddressWithUnit.

    This does the job, but the downside here is that we had to repeat all the other fields from BasicAddress when our changes were purely additive. Instead, we can extend the original BasicAddress type and just add the new fields that are unique to AddressWithUnit.

    The extends keyword on an interface allows us to effectively copy members from other named types, and add whatever new members we want. This can be useful for cutting down the amount of type declaration boilerplate we have to write, and for signaling intent that several different declarations of the same property might be related. For example, AddressWithUnit didn't need to repeat the street property, and because street originates from BasicAddress, a reader will know that those two types are related in some way.

    interfaces can also extend from multiple types.

    hashtag
    Intersection Types

    interfaces allowed us to build up new types from other types by extending them. TypeScript provides another construct called intersection types that is mainly used to combine existing object types.

    An intersection type is defined using the & operator.

    Here, we've intersected Colorful and Circle to produce a new type that has all the members of Colorful and Circle.

    hashtag
    Interfaces vs. Intersections

    We just looked at two ways to combine types which are similar, but are actually subtly different. With interfaces, we could use an extends clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias. The principle difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you'd pick one over the other between an interface and a type alias of an intersection type.

    hashtag
    Generic Object Types

    Let's imagine a Box type that can contain any value - strings, numbers, Giraffes, whatever.

    Right now, the contents property is typed as any, which works, but can lead to accidents down the line.

    We could instead use unknown, but that would mean that in cases where we already know the type of contents, we'd need to do precautionary checks, or use error-prone type assertions.

    One type safe approach would be to instead scaffold out different Box types for every type of contents.

    But that means we'll have to create different functions, or overloads of functions, to operate on these types.

    That's a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frustrating, since our box types and overloads are all effectively the same.

    Instead, we can make a generic Box type which declares a type parameter.

    You might read this as "A Box of Type is something whose contents have type Type". Later on, when we refer to Box, we have to give a type argument in place of Type.

    Think of Box as a template for a real type, where Type is a placeholder that will get replaced with some other type. When TypeScript sees Box<string>, it will replace every instance of Type in Box<Type> with string, and end up working with something like { contents: string }. In other words, Box<string> and our earlier StringBox work identically.

    Box is reusable in that Type can be substituted with anything. That means that when we need a box for a new type, we don't need to declare a new Box type at all (though we certainly could if we wanted to).

    This also means that we can avoid overloads entirely by instead using [generic functions](https: //www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions).

    It is worth noting that type aliases can also be generic. We could have defined our new Box<Type> interface, which was:

    by using a type alias instead:

    Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.

    We'll circle back to type aliases in just a little bit.

    hashtag
    TheArrayType

    Generic object types are often some sort of container type that work independently of the type of elements they contain. It's ideal for data structures to work this way so that they're re-usable across different data types.

    It turns out we've been working with a type just like that throughout this handbook: the Array type. Whenever we write out types like number[] or string[], that's really just a shorthand for Array<number> and Array<string>.

    Much like the Box type above, Array itself is a generic type.

    Modern JavaScript also provides other data structures which are generic, like Map<K, V>, Set<T>, and Promise<T>. All this really means is that because of how Map, Set, and Promise behave, they can work with any sets of types.

    hashtag
    TheReadonlyArrayType

    The ReadonlyArray is a special type that describes arrays that shouldn't be changed.

    Much like the readonly modifier for properties, it's mainly a tool we can use for intent. When we see a function that returns ReadonlyArrays, it tells us we're not meant to change the contents at all, and when we see a function that consumes ReadonlyArrays, it tells us that we can pass any array into that function without worrying that it will change its contents.

    Unlike Array, there isn't a ReadonlyArray constructor that we can use.

    Instead, we can assign regular Arrays to ReadonlyArrays.

    Just as TypeScript provides a shorthand syntax for Array<Type> with Type[], it also provides a shorthand syntax for ReadonlyArray<Type> with readonly Type[].

    One last thing to note is that unlike the readonly property modifier, assignability isn't bidirectional between regular Arrays and ReadonlyArrays.

    hashtag
    Tuple Types

    A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.

    Here, StringNumberPair is a tuple type of string and number. Like ReadonlyArray, it has no representation at runtime, but is significant to TypeScript. To the type system, StringNumberPair describes arrays whose 0 index contains a string and whose 1 index contains a number.

    If we try to index past the number of elements, we'll get an error.

    We can also [destructure tuples](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring) using JavaScript's array destructuring.

    Tuple types are useful in heavily convention-based APIs, where each element's meaning is "obvious". This gives us flexibility in whatever we want to name our variables when we destructure them. In the above example, we were able to name elements 0 and 1 to whatever we wanted.

    However, since not every user holds the same view of what's obvious, it may be worth reconsidering whether using objects with descriptive property names may be better for your API.

    Other than those length checks, simple tuple types like these are equivalent to types which are versions of Arrays that declare properties for specific indexes, and that declare length with a numeric literal type.

    Another thing you may be interested in is that tuples can have optional properties by writing out a question mark (? after an element's type). Optional tuple elements can only come at the end, and also affect the type of length.

    Tuples can also have rest elements, which have to be an array/tuple type.

    • StringNumberBooleans describes a tuple whose first two elements are string and number respectively, but which may have any number of booleans following.

    • StringBooleansNumber describes a tuple whose first element is string and then any number of

    A tuple with a rest element has no set "length" - it only has a set of well-known elements in different positions.

    Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with parameter lists. Tuples types can be used in [rest parameters and arguments](https: //www.typescriptlang.org/docs/handbook/2/functions.html#rest-parameters-and-arguments), so that the following:

    is basically equivalent to:

    This is handy when you want to take a variable number of arguments with a rest parameter, and you need a minimum number of elements, but you don't want to introduce intermediate variables.

    hashtag
    readonlyTuple Types

    One final note about tuple types - tuples types have readonly variants, and can be specified by sticking a readonly modifier in front of them - just like with array shorthand syntax.

    As you might expect, writing to any property of a readonly tuple isn't allowed in TypeScript.

    Tuples tend to be created and left un-modified in most code, so annotating types as readonly tuples when possible is a good default. This is also important given that array literals with const assertions will be inferred with readonly tuple types.

    Here, distanceFromOrigin never modifies its elements, but expects a mutable tuple. Since point's type was inferred as readonly [3, 4], it won't be compatible with [number, number] since that type can't guarantee point's elements won't be mutated.

    Avatar
    Avatar
    Avatar
    <dl>
        <dt>If contact by phone is your preference, what time of day is best?</dt>
        <dd>Daytime</dd>
    </dl>
    <!--
      Hero Section wrapper
      - Component below hero is required to use a white background color
    -->
    
    <section class="relative 2xl:px-24 2xl:pt-24">
    
      <!--
        Hero Container
      -->
    
      <div class="relative container-5xl flex flex-col justify-center aspect-16/12 sm:aspect-16/9 md:aspect-16/7 lg:aspect-16/6 xl:aspect-16/5 px-24 sm:px-32 md:px-48 py-64 md:py-48">
    
        <!--
          - Leave alt attribute empty
          - Use 'srcset' and 'sizes' attributes to optimize the hero image
        -->
    
        <img
          class="absolute top-0 left-0 object-cover object-right w-full h-full 2xl:rounded-lg bg-gray-light"
          src="image.jpg?w=800&as=1&bc=ffffff"
          srcset="image.jpg?w=800&as=1&bc=ffffff 800w,
                  image.jpg?w=1600&as=1&bc=ffffff 1600w"
          sizes="(min-width: 768px) 1600px, 800px"
          alt=""
          width="1600"
          height="500"
          loading="lazy"
        >
    
        <!--
          Overlay
          - Change background gradient colors based on theme of hero
          - Default - "from-blue-dark to-blue sm:via-blue"
        -->
    
        <div
          class="absolute top-0 left-0 h-full w-full 2xl:rounded-l-lg md:w-3/4 bg-gradient-to-tr md:bg-gradient-to-r md:to-transparent opacity-80"
          aria-hidden="true"
        ></div>
    
        <!--
          Content Container
        -->
    
        <div class="relative w-full container-xs md:container-4xl">
    
          <!--
            If dark theme/gradient, text is white
              - Use "text-white"
            If light theme/gradient, text can be gray or blue
              - Use "text-gray-dark" -or- "text-blue"
            Default
              - "text-white"
          -->
    
          <div class="w-full md:w-1/2 text-center md:text-left">
    
            <!-- Hero Title
              - If this is the page title, you should use an <h1> element
              - Use an <h2> element if you already have a page title on the page
            -->
            <h2
              class="text-3xl md:text-2xl-fixed xl:text-3xl"
              id="hero-title"
            >
              Hero Title
            </h2>
    
            <!-- Hero Short Description-->
            <p class="text-lg xl:text-xl mt-12 lg:mt-16">
              Description goes here.
            </p>
    
            <!-- Hero Actions -->
            <div class="flex justify-center md:justify-start gap-16 lg:gap-24 mt-16 lg:mt-24">
              <!--
                If dark theme/gradient
                  - Use "btn-primary-reversed"
                If light theme/gradient
                  - Use "btn-primary"
                Default
                  - "btn-primary-reversed"
              -->
              <a
                class="btn btn-primary-reversed btn-md"
                href="#"
                id="hero-primary-action"
                aria-labelledby="hero-primary-action hero-title"
              >
                Primary Action
              </a>
              <!--
                If dark theme/gradient
                  - Use "btn-secondary-reversed"
                If light theme/gradient
                  - Use "btn-secondary"
                Default
                  - "btn-secondary-reversed"
              -->
              <a
                class="btn btn-secondary-reversed btn-sm lg:btn-md"
                href="#"
                id="hero-secondary-action"
                aria-labelledby="hero-secondary-action hero-title"
              >
                Secondary Action
              </a>
            </div>
          </div>
    
        </div>
    
      </div>
    
    </section>
    npm run storybook
    function greet(person: { name: string; age: number }) {  return "Hello " + person.name;}Try
    interface Person {  name: string;  age: number;} function greet(person: Person) {  return "Hello " + person.name;}Try
    <Route path="/count">
    <Count />
    </Route>
    <Route path="/">
    <Home />
    </Route>
    </Switch>
    </div>
    </BrowserRouter>
    );
    }
    <input type="text" onChange={handleChange}></input>
    </>;
    }

    Field - The name of the field on that item that needs to be modified. If you specify the value of DELETEME, this item will be DELETED from Sitecore.

    Dev, Test, UAT, HotFix, QA, PFT - Each of these columns contains the change that is need for that specific environment. This is the REPLACEMENT value.

    Prod - The value that is being search for which will be replaced by one of the lower environment column valued.

    So how does this work? If you have a value that needs to be updated, you will need to add a row and specify ALL the values. ALL the columns are required. In many instanced, the column values may be the same (for instance, Test and UAT and Hotfix may be all the same).

    Now here is the caveat. The contents of the Prod column is the driver. That value MUST exist in the lower environments field that is to be replaced (or it will not update). Also, this column value DOES NOT have to be the complete value for the Production field. Only the value that needs to be replaced. Ok, so that is confusing so lets show a couple of examples.

    Example 1:

    There is a field "Google Analytics Body Code" that contains this complete value:

    <!-- Google Tag Manager (noscript) retainstyle--> <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-N6GDT9" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <!-- End Google Tag Manager (noscript)-->

    We need to have the value in Test/UAT/Hotfix modified to this:

    <!-- Google Tag Manager (noscript) --> <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-N6GDT9&gtm_auth=3voUst9VwgohH28csS13-g&gtm_preview=env-1559&gtm_cookies_win=x" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <!-- End Google Tag Manager (noscript) -->

    If you look at the entire value, there is only one change. The value of id=GTM-N6GDT9 has come content appended to it. So in the spreadsheet, the Prod column should specify:

    id=GTM-N6GDT9

    And the Test and UAT and Hotfix columns should specify:

    id=GTM-N6GDT9&gtm_auth=3voUst9VwgohH28csS13-g&gtm_preview=env-1559&gtm_cookies_win=x

    There is no need to specify the entire contents. Just specify the value that needs to be change and then the value to be changed to.

    Example 2:

    There is a field "Link URL" that contains this complete value:

    <link text="Register Now" linktype="external" url="https://progress-energy.com/app/LoginRegistration/register.aspx?RC=progress-energy.com%2Fapp%2FUtilityConsultant%2Flist.aspx" anchor="" target="" />

    We need to have the value in Test/UAT/Hotfix modified to this:

    <link text="Register Now" linktype="external" url="https://dev.progress-energy.com/app/LoginRegistration/register.aspx?RC=dev.progress-energy.com%2Fapp%2FUtilityConsultant%2Flist.aspx" anchor="" target="" />

    If you look at the entire value, there is only one change. The value of the url:

    "https://progress-energy.com/app/LoginRegistration/register.aspx?RC=progress-energy.com%2Fapp%2FUtilityConsultant%2Flist.aspx

    And the Test and UAT and Hotfix columns should specify:

    https://dev.progress-energy.com/app/LoginRegistration/register.aspx?RC=dev.progress-energy.com%2Fapp%2FUtilityConsultant%2Flist.aspxarrow-up-right

    There is no need to specify the entire contents. Just specify the value that needs to be change and then the value to be changed to.

    The ability to only need to specify what needs to be changed vs the entire field is quite noticeable on certain very large fields (Rich Text).

    elements immediately follow each other.
  • Check that the description for each term is contained in one or more dd elements.

  • Check that the one or more dd elements immediately follow the one or more dt elements containing the term being described.

  • <dl title="Nautical terms">
      <dt>Knot</dt>
      <dd>
        <p>A <em>knot</em> is a unit of speed equaling 1
          nautical mile per hour (1.15 miles per hour or 1.852
          kilometers per hour).</p>
      </dd>
      <dt>Port</dt>
      <dd>
        <p><em>Port</em> is the nautical term (used on
          boats and ships) that refers to the left side
          of a ship, as perceived by a person facing towards
          the bow (the front of the vessel).</p>
      </dd>
      <dt>Starboard</dt>
      <dd>
        <p><em>Starboard</em> is the nautical term (used
          on boats and ships) that refers to the right
          side of a vessel, as perceived by a person
          facing towards the bow (the front of the vessel).</p>
      </dd>
    </dl>
    Success Criterion 3.1.3: Unusual Wordsarrow-up-right
    G55: Linking to definitionsarrow-up-right
    HTML5 Description listsarrow-up-right
    HTML 4 Definition lists: the DL, DT, and DD elementsarrow-up-right
    G62: Providing a glossaryarrow-up-right
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope
    name@example.comenvelope

    [Component Story Format](https: //storybook.js.org/blog/component-story-format/) - read more about the Component Story Format (CSF), a new way to author stories based on ES6 modules

    function MyDiv(props) {
      if (props.layout === "horizontal") {
        // BAD! Because you know for sure "layout" is not a prop that <div> understands.
        return <div {...props} style={getHorizontalStyle()} />;
      } else {
        // BAD! Because you know for sure "layout" is not a prop that <div> understands.
        return <div {...props} style={getVerticalStyle()} />;
      }
    }
    on MDNarrow-up-right
    boolean
    s and ending with a
    number
    .
  • BooleansStringNumber describes a tuple whose starting elements any number of booleans and ending with a string then a number.

  • export const NoIcons = Template.bind();
    NoIcons.args = {
      items: itemsOptions["3"],
    };
    export const Icons = Template.bind({});
    Icons.args = {
      items: itemsOptions["5"],
    };
    export default {
      title: "Components/Accordion",
      component: AccordionComponent,
      argTypes: {
        theme: {
          name: "Theme",
        },
        closeOthers: {
          name: "One Panel Open At A Time",
        },
      },
    };
    
    const Template = (args) => <AccordionComponent {...args} />;
    
    export const Primary = Template.bind({});
    
    Primary.args = {
      ...props,
    };
    argTypes: {
      backgroundColor: {
        control: {
          type: 'radio',
          options: ['white', 'gray'],
        },
        defaultValue: 'white',
      },
    },
    argTypes: {
      items: {
        name: 'How many items?',
        control: {
          type: 'select',
          options: {
            ...itemsPrimary,
          },
        },
      },
    },
    argTypes: {
      title: {
        control: {
          type: 'text',
        },
      },
      subtitle: {
        control: {
          type: 'text',
        },
      },
      link: {
        control: 'boolean',
      },
      ctaType: {
        control: {
          type: 'inline-radio',
          options: ['ImageSlide', 'VideoSlide'],
        },
      },
    },
    import React from "react";
    import MyComponent from "./index";
    import { Data } from "./data";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    
    const props = MyComponentComposition({ fields: Data });
    
    export default {
      title: "Components/MyComponent",
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: "radio",
            options: ["white", "gray"],
          },
          defaultValue: "white",
        },
      },
    };
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    import React from 'react';
    import MyComponent from './index';
    import { Data } from './data';
    
    ...
    ...
    
    import { MyComponent as MyComponentComposition } from '../../lib/composition';
    
    
    const props = MyComponentComposition({ fields: Data });
    
    ...
    ...
    
    export default {
      title: 'Components/MyComponent',
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: 'radio',
            options: ['white', 'gray'],
          },
          defaultValue: 'white',
        },
      },
    };
    
    ...
    ...
    
    
    const Template = args => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    ...
    
    import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';
    
    
    const props = BulletedOverviewComposition(Data);
    
    const itemsToShow = createStoryOptions(props.items);
    
    export default {
      title: 'Components/Bulleted Overview',
      component: BulletedOverview,
      argTypes: {
        ...storybookCustomArgs(props, BulletedOverview, [], false),
        items: {
          name: 'How many items?',
          control: {
            type: 'select',
            options: {
              ...itemsToShow,
            },
          },
        },
      },
    };
    
    ...
    function MyDiv(props) {
      const { layout, ...rest } = props;
      if (layout === "horizontal") {
        return <div {...rest} style={getHorizontalStyle()} />;
      } else {
        return <div {...rest} style={getVerticalStyle()} />;
      }
    }
    function MyDiv(props) {
      const divProps = Object.assign({}, props);
      delete divProps.layout;
    
      if (props.layout === "horizontal") {
        return <div {...divProps} style={getHorizontalStyle()} />;
      } else {
        return <div {...divProps} style={getVerticalStyle()} />;
      }
    }
    type Person = {  name: string;  age: number;}; function greet(person: Person) {  return "Hello " + person.name;}Try
    interface PaintOptions {  shape: Shape;  xPos?: number;  yPos?: number;} function paintShape(opts: PaintOptions) {
    // ...}
    const shape = getShape();paintShape({ shape });paintShape({ shape, xPos: 100 });paintShape({ shape, yPos: 100 });paintShape({ shape, xPos: 100, yPos: 100 });Try
    function paintShape(opts: PaintOptions) {  let xPos = opts.xPos;                   (property) PaintOptions.xPos?: number | undefined  let yPos = opts.yPos;                   (property) PaintOptions.yPos?: number | undefined
    // ...}Try
    function paintShape(opts: PaintOptions) {  let xPos = opts.xPos === undefined ? 0 : opts.xPos;       let xPos: number  let yPos = opts.yPos === undefined ? 0 : opts.yPos;       let yPos: number
    // ...}Try
    function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {  console.log("x coordinate at", xPos);                                  (parameter) xPos: number  console.log("y coordinate at", yPos);                                  (parameter) yPos: number
    // ...}Try
    function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {  render(shape);Cannot find name 'shape'. Did you mean 'Shape'?Cannot find name 'shape'. Did you mean 'Shape'?  render(xPos);Cannot find name 'xPos'.Cannot find name 'xPos'.}Try
    interface SomeType {  readonly prop: string;} function doSomething(obj: SomeType) {
    // We can read from 'obj.prop'.  console.log(`prop has the value '${obj.prop}'.`);
    // But we can't re-assign it.  obj.prop = "hello";Cannot assign to 'prop' because it is a read-only property.Cannot assign to 'prop' because it is a read-only property.}Try
    interface Home {  readonly resident: { name: string; age: number };} function visitForBirthday(home: Home) {
    // We can read and update properties from 'home.resident'.  console.log(`Happy birthday ${home.resident.name}!`);  home.resident.age++;} function evict(home: Home) {
    // But we can't write to the 'resident' property itself on a 'Home'.  home.resident = {Cannot assign to 'resident' because it is a read-only property.Cannot assign to 'resident' because it is a read-only property.    name: "Victor the Evictor",    age: 42,  };}Try
    interface Person {  name: string;  age: number;} interface ReadonlyPerson {  readonly name: string;  readonly age: number;} let writablePerson: Person = {  name: "Person McPersonface",  age: 42,};
    // workslet readonlyPerson: ReadonlyPerson = writablePerson; console.log(readonlyPerson.age);
    // prints '42'writablePerson.age++;console.log(readonlyPerson.age);
    // prints '43'Try
    interface StringArray {  [index: number]: string;}
    const myArray: StringArray = getStringArray();
    const secondItem = myArray[1];
    const secondItem: stringTry
    interface NumberDictionary {  [index: string]: number;   length: number;
    // ok  name: string;Property 'name' of type 'string' is not assignable to 'string' index type 'number'.Property 'name' of type 'string' is not assignable to 'string' index type 'number'.}Try
    interface NumberOrStringDictionary {  [index: string]: number | string;  length: number;
    // ok, length is a number  name: string;
    // ok, name is a string}Try
    interface ReadonlyStringArray {  readonly [index: number]: string;} let myArray: ReadonlyStringArray = getReadOnlyStringArray();myArray[2] = "Mallory";Index signature in type 'ReadonlyStringArray' only permits reading.Index signature in type 'ReadonlyStringArray' only permits reading.Try
    interface BasicAddress {  name?: string;  street: string;  city: string;  country: string;  postalCode: string;}Try
    interface AddressWithUnit {  name?: string;  unit: string;  street: string;  city: string;  country: string;  postalCode: string;}Try
    interface BasicAddress {  name?: string;  street: string;  city: string;  country: string;  postalCode: string;} interface AddressWithUnit extends BasicAddress {  unit: string;}Try
    interface Colorful {  color: string;} interface Circle {  radius: number;} interface ColorfulCircle extends Colorful, Circle {}
    const cc: ColorfulCircle = {  color: "red",  radius: 42,};Try
    interface Colorful {  color: string;}interface Circle {  radius: number;} type ColorfulCircle = Colorful & Circle;Try
    function draw(circle: Colorful & Circle) {  console.log(`Color was ${circle.color}`);  console.log(`Radius was ${circle.radius}`);}
    // okaydraw({ color: "blue", radius: 42 });
    // oopsdraw({ color: "red", raidus: 42 });Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
      Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
      Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?Try
    interface Box {  contents: any;}Try
    interface Box {  contents: unknown;} let x: Box = {  contents: "hello world",};
    // we could check 'x.contents'if (typeof x.contents === "string") {  console.log(x.contents.toLowerCase());}
    // or we could use a type assertionconsole.log((x.contents as string).toLowerCase());Try
    interface NumberBox {  contents: number;} interface StringBox {  contents: string;} interface BooleanBox {  contents: boolean;}Try
    function setContents(box: StringBox, newContents: string): void;function setContents(box: NumberBox, newContents: number): void;function setContents(box: BooleanBox, newContents: boolean): void;function setContents(box: { contents: any }, newContents: any) {  box.contents = newContents;}Try
    interface Box<Type> {  contents: Type;}Try
    let box: Box<string>;Try
    interface Box<Type> {  contents: Type;}interface StringBox {  contents: string;} let boxA: Box<string> = { contents: "hello" };boxA.contents;        (property) Box<string>.contents: string let boxB: StringBox = { contents: "world" };boxB.contents;        (property) StringBox.contents: stringTry
    interface Box<Type> {  contents: Type;} interface Apple {
    // ....}
    // Same as '{ contents: Apple }'.type AppleBox = Box<Apple>;Try
    function setContents<Type>(box: Box<Type>, newContents: Type) {  box.contents = newContents;}Try
    interface Box<Type> {  contents: Type;}Try
    type Box<Type> = {  contents: Type;};Try
    type OrNull<Type> = Type | null; type OneOrMany<Type> = Type | Type[]; type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;           type OneOrManyOrNull<Type> = OneOrMany<Type> | null type OneOrManyOrNullStrings = OneOrManyOrNull<string>;               type OneOrManyOrNullStrings = OneOrMany<string> | nullTry
    function doSomething(value: Array<string>) {
    // ...} let myArray: string[] = ["hello", "world"];
    // either of these work!doSomething(myArray);doSomething(new Array("hello", "world"));Try
    interface Array<Type> {  /**   * Gets or sets the length of the array.   */  length: number;   /**   * Removes the last element from an array and returns it.   */  pop(): Type | undefined;   /**   * Appends new elements to an array, and returns the new length of the array.   */  push(...items: Type[]): number;
    // ...}Try
    function doStuff(values: ReadonlyArray<string>) {
    // We can read from 'values'...
    const copy = values.slice();  console.log(`The first value is ${values[0]}`);
    // ...but we can't mutate 'values'.  values.push("hello!");Property 'push' does not exist on type 'readonly string[]'.Property 'push' does not exist on type 'readonly string[]'.}Try
    new ReadonlyArray("red", "green", "blue");'ReadonlyArray' only refers to a type, but is being used as a value here.'ReadonlyArray' only refers to a type, but is being used as a value here.Try
    const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
    Try;
    function doStuff(values: readonly string[]) {
    // We can read from 'values'...
    const copy = values.slice();  console.log(`The first value is ${values[0]}`);
    // ...but we can't mutate 'values'.  values.push("hello!");Property 'push' does not exist on type 'readonly string[]'.Property 'push' does not exist on type 'readonly string[]'.}Try
    let x: readonly string[] = [];let y: string[] = []; x = y;y = x;The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.Try
    type StringNumberPair = [string, number];Try
    function doSomething(pair: [string, number]) {
    const a = pair[0];
    const a: string
    const b = pair[1];
    const b: number
    // ...} doSomething(["hello", 42]);Try
    function doSomething(pair: [string, number]) {
    // ...
    const c = pair[2];Tuple type '[string, number]' of length '2' has no element at index '2'.Tuple type '[string, number]' of length '2' has no element at index '2'.}Try
    function doSomething(stringHash: [string, number]) {
    const [inputString, hash] = stringHash;   console.log(inputString);
    const inputString: string   console.log(hash);
    const hash: number}Try
    interface StringNumberPair {
    // specialized properties  length: 2;  0: string;  1: number;
    // Other 'Array<string | number>' members...  slice(start?: number, end?: number): Array<string | number>;}Try
    type Either2dOr3d = [number, number, number?]; function setCoordinate(coord: Either2dOr3d) {
    const [x, y, z] = coord;
    const z: number | undefined   console.log(`Provided coordinates had ${coord.length} dimensions`);                                                  (property) length: 2 | 3}Try
    type StringNumberBooleans = [string, number, ...boolean[]];type StringBooleansNumber = [string, ...boolean[], number];type BooleansStringNumber = [...boolean[], string, number];Try
    const a: StringNumberBooleans = ["hello", 1];
    const b: StringNumberBooleans = ["beautiful", 2, true];
    const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];
    Try;
    function readButtonInput(...args: [string, number, ...boolean[]]) {
    const [name, version, ...input] = args;
    // ...}Try
    function readButtonInput(name: string, version: number, ...input: boolean[]) {
    // ...}Try
    function doSomething(pair: readonly [string, number]) {
    // ...}Try
    function doSomething(pair: readonly [string, number]) {  pair[0] = "hello!";Cannot assign to '0' because it is a read-only property.Cannot assign to '0' because it is a read-only property.}Try
    let point = [3, 4] as
    const; function distanceFromOrigin([x, y]: [number, number]) {  return Math.sqrt(x ** 2 + y ** 2);} distanceFromOrigin(point);Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.
      The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.
      The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.Try
    Unable to find an element with the text: Hello world.
    This could be because the text is broken up by multiple elements.
    In this case, you can provide a function for your text
    matcher to make your matcher more flexible.
    <div>Hello <span>world</span></div>
    Enzymearrow-up-right
    DOM nodesarrow-up-right
    Guiding Principlearrow-up-right
    querySelectorarrow-up-right
    closestarrow-up-right
    debugarrow-up-right
    withinarrow-up-right
    textContentarrow-up-right
    import React from "react";
    import { render, screen } from "@testing-library/react";
    
    test("everything is a node", () => {
      const Foo = () => <div>Hello</div>;
      render(<Foo />);
      expect(screen.getByText("Hello")).toBeInstanceOf(Node);
    });
    import React from "react";
    import { render, screen } from "@testing-library/react";
    
    test("the button has type of reset", () => {
      const ResetButton = () => (
        <button type="reset">
          <div>Reset</div>
        </button>
      );
      render(<ResetButton />);
      const node = screen.getByText("Reset");
    
      // This won't work because `node` is the `<div>`
      // expect(node).toHaveProperty("type", "reset");
    
      expect(node.closest("button")).toHaveProperty("type", "reset"
    });
    const { debug } = render(<MyComponent />);
    debug();
    const { debug } = render(<MyComponent />);
    const button = screen.getByText("Click me").closest();
    debug(button);
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Fruit</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>Apples</td>
        </tr>
        <tr>
          <td>2</td>
          <td>Oranges</td>
        </tr>
        <tr>
          <td>3</td>
          <td>Apples</td>
        </tr>
      </tbody>
    </tabl
    import React from "react";
    import { render, screen, within } from "@testing-library/react"; // highlight-line
    import "jest-dom/extend-expect";
    
    test("the values are in the table", () => {
      const MyTable = ({ values }) => (
        <table>
          <thead>
            <tr>
              <th>ID</th>
              <th>Fruits</th>
            </tr>
          </thead>
          <tbody>
            {values.map(([id, fruit]) => (
              <tr key={id}>
                <td>{id}</td>
                <td>{fruit}</td>
              </tr>
            ))}
          </tbody>
        </table>
      );
      const values = [
        ["1", "Apples"],
        ["2", "Oranges"],
        ["3", "Apples"],
      ];
      render(<MyTable values={values} />);
    
      values.forEach(([id, fruit]) => {
        const row = screen.getByText(id).closest("tr");
        // highlight-start
        const utils = within(row);
        expect(utils.getByText(id)).toBeInTheDocument();
        expect(utils.getByText(fruit)).toBeInTheDocument();
        // highlight-end
      });
    });
    j;
    import { render, screen, within } from "@testing-library/react";
    import "jest-dom/extend-expect";
    
    test("pass functions to matchers", () => {
      const Hello = () => (
        <div>
          Hello <span>world</span>
        </div>
      );
      render(<Hello />);
    
      // These won't match
      // getByText("Hello world");
      // getByText(/Hello world/);
    
      screen.getByText((content, node) => {
        const hasText = (node) => node.textContent === "Hello world";
        const nodeHasText = hasText(node);
        const childrenDontHaveText = Array.from(node.children).every(
          (child) => !hasText(child)
        );
    
        return nodeHasText && childrenDontHaveText;
      });
    });
    fireEvent.change(input, { target: { value: "Hello world" } });
    import userEvent from "@testing-library/user-event";
    
    userEvent.type(input, "Hello world");
    Reportsarrow-up-right
    Issuesarrow-up-right
    Estimatesarrow-up-right
    Componentsarrow-up-right
    Delete this attachmentarrow-up-right
    Screen Shot 2022-03-28 at 4.36.23 PM.pngarrow-up-right
    Historyarrow-up-right
    Activityarrow-up-right
    Time In Statusarrow-up-right
    2 pull requestsarrow-up-right
    Create brancharrow-up-right
    Story - Created by Jira Software - do not edit or delete. Issue type for a user story.
    Medium - Has the potential to affect progress.
    Medium - Has the potential to affect progress.
    Avatar
    Avatar
    Avatar
    Avatar
    Avatar
    Avatar
    Avatar
    Avatar
    // # TextMatch type accenpts function: https://testing-library.com/docs/queries/about/#textmatch
    import { renderWithCTX, screen, Matcher } from "src/lib/testWrappers";
    import userEvent from "@testing-library/user-event";
    import "@testing-library/jest-dom";
    import { Accordion, themeMap } from "./index";
    import { compositionFunction } from "./composition";
    import data from "./data";
    import { stripHTMLTags } from "src/lib/helpers";
    jest.mock("src/lib/useIntersection");
    describe("Accordion", () => {
      const props = compositionFunction(data);
      it.skip("should render the correct items", () => {
        renderWithCTX(<Accordion {...props} />);
        for (let i = 0; i < props.items.length - 1; i++) {
          const accordionTitle = screen.getByText(
            props?.items[0]?.title?.value as string
          );
          expect(accordionTitle).toBeTruthy();
        }
      });
      it.skip("should render Accordion Component with image", () => {
        renderWithCTX(<Accordion {...props} />);
        const img = screen.getByRole("img", { name: /facebook/i });
        expect(img).toBeInTheDocument();
      });
      it("should reveal text when user clicks", async () => {
        renderWithCTX(<Accordion {...props} />);
        const accordionButton = screen.getByRole("button", {
          name: props.items[0].title?.value,
        });
        // # Full Manual Query:
        // (This is actually not good - getByText won't respect eg aria- attrs like getByRole so we aren't actually testing anything)
        // These are [not exposed to a11y tree](https://testing-library.com/docs/queries/about/#priority)
        // 👉
        // screen.getByText(
        //   /proin laoreet mauris vel urna tempor ultricies\. duis rhoncus lorem sed tellus egestas bibendum\. in aliquam mauris est, vel condimentum metus aliquet a\. nunc volutpat tincidunt nisl luctus pretium\. sagittis elit non, vestibulum metus\. mauris maximus vitae magna in mattis\. integer interdum maximus felis sed placerat\. nam lobortis tellus non felis fermentum, vitae venenatis ligula congue\. donec sit amet luctus odio\. magna commodo, sodales convallis ante scelerisque\. etiam ipsum lorem, rhoncus a sapien id, placerat consequat nunc\. curabitur in orci libero\. morbi tincidunt ante vel sem rutrum tempor\. cras ac purus quis urna maximus volutpat\./i
        // );
        // # We can still do a findByText,
        // but we need to check ourselves whether it is accessible:
        // 👉
        const hiddenText = await screen.findByText((content, element) => {
          const hasTextContent =
            element?.innerHTML === props?.items[0]?.text?.value;
          return hasTextContent;
        });
        expect(hiddenText).toBeInTheDocument();
        expect(hiddenText).toHaveAttribute("aria-hidden", "true");
        await userEvent.click(accordionButton);
        // # Compare against textContent stripHTMLTags:
        // (similar to https://polvara.me/posts/five-things-you-didnt-know-about-testing-library)
        // 👉
        // const noTag = stripHTMLTags(props?.items?.[0]?.text?.value as string);
        // const [revealedText] = await screen.findAllByText(
        //   // @ts-ignore
        //   // eslint-disable-next-line
        //   (content, element) => element?.textContent === noTag
        //   // ?  console.log('🔥', content) || true : false
        // );
        // expect(revealedText).toBeInTheDocument();
        // console.log('😳', noTag);
        // 😳 Proin laoreet mauris vel urna tempor ultricies. Duis rhoncus lorem sed tellus egestas bibendum. In aliquam mauris est, vel condimentum metus aliquet a. Nunc volutpat tincidunt nisl luctus pretium. Duis et ligula semper, sagittis elit non, vestibulum metus. Mauris maximus vitae magna in mattis. Integer interdum maximus felis sed placerat. Nam lobortis tellus non felis fermentum, vitae venenatis ligula congue. Donec sit amet luctus odio. Nam convallis justo vitae magna commodo, sodales convallis ante scelerisque. Etiam ipsum lorem, rhoncus a sapien id, placerat consequat nunc. Curabitur in orci libero. Morbi tincidunt ante vel sem rutrum tempor. Cras ac purus quis urna maximus volutpat.
        // # Find by innerHTML:
        // 👉
        const revealedText = await screen.findByText((content, element) => {
          const hasTextContent =
            element?.innerHTML === props?.items[0]?.text?.value;
          return hasTextContent;
        });
        expect(revealedText).toBeInTheDocument();
        expect(revealedText).toHaveAttribute("aria-hidden", "false");
      });
      // Overall, probably ultimately more durable / less error prone to use a testid and check aria- attrs
      it.skip("renders the correct default style", () => {
        const { rerender } = renderWithCTX(<Accordion {...props} />);
        const button = screen.getByRole("button", {
          name: /accordion trigger one/i,
        });
        expect(button).toHaveClass(themeMap.default.button);
        const altThemeProp = { ...props, theme: "footer" } as const;
        rerender(<Accordion {...altThemeProp} />);
        expect(button).toHaveClass(themeMap?.footer?.button);
      });
    });

    🕵♂ Testing a Custom Select with React Testing Library

    Most Useful

    )
    ;
    Avatar

    ByRole

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByRole, queryByRole, getAllByRole, queryAllByRole, findByRole, findAllByRole

    hashtag
    API

    Queries for elements with the given role (and it also accepts a TextMatch). Default roles are taken into consideration e.g. <button /> has the button role without explicitly setting the role attribute. Here you can see .

    Please note that setting a role and/or aria-* attribute that matches the implicit ARIA semantics is unnecessary and is not recommended as these properties are already set by the browser, and we must not use the role and aria-* attributes in a manner that conflicts with the semantics described. For example, a button element can't have the role attribute of heading, because the button element has default characteristics that conflict with the heading role.

    Roles are matched literally by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like checkbox will not include elements with a subclass role like switch.

    You can query the returned element(s) by their . The accessible name is for simple cases equal to e.g. the label of a form element, or the text content of a button, or the value of the aria-label attribute. It can be used to query a specific element if multiple elements with the same role are present on the rendered content. For an in-depth guide check out . If you only query for a single element with getByText('The name') it's oftentimes better to use getByRole(expectedRole, { name: 'The name' }). The accessible name query does not replace other queries such as *ByAlt or *ByTitle. While the accessible name can be equal to these attributes, it does not replace the functionality of these attributes. For example <img aria-label="fancy image" src="fancy.jpg" /> will be returned for both getByAltText('fancy image') and getByRole('img', { name: 'fancy image' }). However, the image will not display its description if fancy.jpg

    hashtag
    Options

    hashtag
    hidden

    If you set hidden to true elements that are normally excluded from the accessibility tree are considered for the query as well. The default behavior follows https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion with the exception of role="none" and role="presentation" which are considered in the query in any case. For example in

    getByRole('button') would only return the Close dialog-button. To make assertions about the Open dialog-button you would need to use getAllByRole('button', { hidden: true }).

    The default value for hidden can be configured.

    hashtag
    selected

    You can filter the returned elements by their selected state by setting selected: true or selected: false.

    For example in

    you can get the "Native"-tab by calling getByRole('tab', { selected: true }). To learn more about the selected state and which elements can have this state see .

    hashtag
    checked

    You can filter the returned elements by their checked state by setting checked: true or checked: false.

    For example in

    you can get the "Sugar" option by calling getByRole('checkbox', { checked: true }). To learn more about the checked state and which elements can have this state see .

    Note

    Checkboxes have a "mixed" state, which is considered neither checked nor unchecked (details ).

    hashtag
    current

    You can filter the returned elements by their current state by setting current: boolean | string. Note that no aria-current attribute will match current: false since false is the default value for aria-current.

    For example in

    you can get the "👍" link by calling getByRole('link', { current: true }) and the "👎" by calling getByRole('link', { current: false }). To learn more about the current state see .

    hashtag
    pressed

    Buttons can have a pressed state. You can filter the returned elements by their pressed state by setting pressed: true or pressed: false.

    For example in

    you can get the "👍" button by calling getByRole('button', { pressed: true }). To learn more about the pressed state see .

    hashtag
    expanded

    You can filter the returned elements by their expanded state by setting expanded: true or expanded: false.

    For example in

    you can get the "Expandable Menu Item" link by calling getByRole('link', { expanded: false }). To learn more about the expanded state and which elements can have this state see .

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    hashtag
    queryFallbacks

    By default, it's assumed that the first role of each element is supported, so only the first role can be queried. If you need to query an element by any of its fallback roles instead, you can use queryFallbacks: true.

    For example, getByRole('switch') would always match <div role="switch checkbox" /> because it's the first role, while getByRole('checkbox') would not. However, getByRole('checkbox', { queryFallbacks: true }) would enable all fallback roles and therefore match the same element.

    An element doesn't have multiple roles in a given environment. It has a single one. Multiple roles in the attribute are evaluated from left to right until the environment finds the first role it understands. This is useful when new roles get introduced and you want to start supporting those as well as older environments that don't understand that role (yet).

    hashtag
    level

    An element with the heading role can be queried by any heading level getByRole('heading') or by a specific heading level using the level option getByRole('heading', { level: 2 }).

    The level option queries the element(s) with the heading role matching the indicated level determined by the semantic HTML heading elements <h1>-<h6> or matching the aria-level attribute.

    Given the example below,

    you can query the Heading Level Three heading using getByRole('heading', { level: 3 }).

    While it is possible to explicitly set role="heading" and aria-level attribute on an element, it is strongly encouraged to use the semantic HTML headings <h1>-<h6>.

    To learn more about the aria-level property, see .

    The level option is only applicable to the heading role. An error will be thrown when used with any other role.

    hashtag
    description

    You can filter the returned elements by their for those cases where you have several elements with the same role and they don't have an accessible name but they do have a description. This would be the case for elements with role, where the aria-describedby attribute is used to describe the element's content.

    For example in

    You can query a specific element like this

    Setup

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    React Testing Library does not require any configuration to be used. However, there are some things you can do when configuring your testing framework to reduce some boilerplate. In these docs we'll demonstrate configuring Jest, but you should be able to do similar things with any testing frameworkarrow-up-right (React Testing Library does not require that you use Jest).

    hashtag
    Global Config

    Adding options to your global test config can simplify the setup and teardown of tests in individual files.

    hashtag
    Custom Render

    It's often useful to define a custom render method that includes things like global context providers, data stores, etc. To make this available globally, one approach is to define a utility file that re-exports everything from React Testing Library. You can replace React Testing Library with this file in all your imports. See for a way to make your test util file accessible without using relative paths.

    The example below sets up data providers using the wrapper option to render.

    <Tabs groupId="test-utils" defaultValue="jsx" values={[ {label: 'Javascript', value: 'jsx'}, {label: 'Typescript', value: 'tsx'}, ]}>

    Note

    Babel versions lower than 7 throw an error when trying to override the named export in the example above. See and the workaround below.

    chevron-rightWorkaround for Babel 6hashtag

    You can use CommonJS modules instead of ES modules, which should work in Node:

    hashtag
    Add custom queries

    Note

    Generally you should not need to create custom queries for react-testing-library. Where you do use it, you should consider whether your new queries encourage you to test in a user-centric way, without testing implementation details.

    You can define your own custom queries as described in the Custom Queries documentation, or via the buildQueries helper. Then you can use them in any render call using the queries option. To make the custom queries available globally, you can add them to your custom render method as shown below.

    In the example below, a new set of query variants are created for getting elements by data-cy, a "test ID" convention mentioned in the documentation.

    You can then override and append the new queries via the render function by passing a queries option.

    If you want to add custom queries globally, you can do this by defining a custom render method:

    <Tabs groupId="test-utils" defaultValue="jsx" values={[ {label: 'Javascript', value: 'jsx'}, {label: 'Typescript', value: 'tsx'}, ]}>

    You can then use your custom queries as you would any other query:

    hashtag
    Configuring Jest with Test Utils

    To make your custom test file accessible in your Jest test files without using relative imports (../../test-utils), add the folder containing the file to the Jest moduleDirectories option.

    This will make all the .js files in the test-utils directory importable without ../.

    If you're using TypeScript, merge this into your tsconfig.json. If you're using Create React App without TypeScript, save this to jsconfig.json instead.

    hashtag
    Jest 28

    If you're using Jest 28 or later, jest-environment-jsdom package now must be installed separately.

    jsdom is also no longer the default environment. You can enable jsdom globally by editing jest.config.js:

    Or if you only need jsdom in some of your tests, you can enable it as and when needed using :

    hashtag
    Jest 27

    If you're using a recent version of Jest (27), jsdom is no longer the default environment. You can enable jsdom globally by editing jest.config.js:

    Or if you only need jsdom in some of your tests, you can enable it as and when needed using :

    hashtag
    Jest 24 (or lower) and defaults

    If you're using the Jest testing framework version 24 or lower with the default configuration, it's recommended to use jest-environment-jsdom-fifteen package as Jest uses a version of the jsdom environment that misses some features and fixes, required by React Testing Library.

    First, install jest-environment-jsdom-fifteen.

    Then specify jest-environment-jsdom-fifteen as the testEnvironment:

    hashtag
    Using without Jest

    If you're running your tests in the browser bundled with webpack (or similar) then React Testing Library should work out of the box for you. However, most people using React Testing Library are using it with the Jest testing framework with the testEnvironment set to jest-environment-jsdom (which is the default configuration with Jest 26 and earlier).

    jsdom is a pure JavaScript implementation of the DOM and browser APIs that runs in Node. If you're not using Jest and you would like to run your tests in Node, then you must install jsdom yourself. There's also a package called global-jsdom which can be used to setup the global environment to simulate the browser APIs.

    First, install jsdom and global-jsdom.

    With mocha, the test command would look something like this:

    hashtag
    Skipping Auto Cleanup

    Cleanup is called after each test automatically by default if the testing framework you're using supports the afterEach global (like mocha, Jest, and Jasmine). However, you may choose to skip the auto cleanup by setting the RTL_SKIP_AUTO_CLEANUP env variable to 'true'. You can do this with like so:

    To make this even easier, you can also simply import @testing-library/react/dont-cleanup-after-each which will do the same thing. Just make sure you do this before importing @testing-library/react. You could do this with Jest's setupFiles configuration:

    Or with mocha's -r flag:

    Alternatively, you could import @testing-library/react/pure in all your tests that you don't want the cleanup to run and the afterEach won't be setup automatically.

    hashtag
    Auto Cleanup in Mocha's watch mode

    When using Mocha in watch mode, the globally registered cleanup is run only the first time after each test. Therefore, subsequent runs will most likely fail with a TestingLibraryElementError: Found multiple elements error.

    To enable automatic cleanup in Mocha's watch mode, add a cleanup . Create a mocha-watch-cleanup-after-each.js file with the following contents:

    And register it using mocha's -r flag:

    Common Mistakes With RTL

    hashtag
    Not using Testing Library ESLint pluginsarrow-up-right

    Importance: medium

    If you'd like to avoid several of these common mistakes, then the official ESLint plugins could help out a lot:

    Note: If you are using create-react-app, eslint-plugin-testing-library is already included as a dependency.

    Advice: Install and use the ESLint plugin for Testing Library.

    hashtag

    Importance: low

    The name wrapper is old cruft from enzyme and we don't need that here. The return value from render is not "wrapping" anything. It's simply a collection of utilities that (thanks to the next thing) you should actually not often need anyway.

    Advice: destructure what you need from render or call it view.

    hashtag

    Importance: medium

    For a long time now cleanup happens automatically (supported for most major testing frameworks) and you no longer need to worry about it. .

    Advice: don't use cleanup

    hashtag

    Importance: medium

    screen (which means you should have access to it in @testing-library/react@>=9). It comes from the same import statement you get render from:

    The benefit of using screen is you no longer need to keep the render call destructure up-to-date as you add/remove the queries you need. You only need to type screen. and let your editor's magic autocomplete take care of the rest.

    The only exception to this is if you're setting the container or baseElement which you probably should avoid doing (I honestly can't think of a legitimate use case for those options anymore and they only exist for historical reasons at this point).

    You can also call instead of debug

    Advice: use screen for querying and debugging.

    hashtag

    Importance: high

    That toBeDisabled assertion comes from . It's strongly recommended to use jest-dom because the error messages you get with it are much better.

    Advice: install and use **

    hashtag

    Importance: medium

    I see people wrapping things in act like this because they see these "act" warnings all the time and are just desperately trying anything they can to get them to go away, but what they don't know is that render and fireEvent are already wrapped in act! So those are doing nothing useful.

    Most of the time, if you're seeing an act warning, it's not just something to be silenced, but it's actually telling you that something unexpected is happening in your test. You can learn more about this from my blog post (and videos): .

    Advice: Learn when act is necessary and don't wrap things in act unnecessarily.

    hashtag

    Importance: high

    We maintain a page called of the queries you should attempt to use in the order you should attempt to use them. If your goal is aligned with ours of having tests that give you confidence that your app will work when your users use them, then you'll want to query the DOM as closely to the way your end-users do so as possible. The queries we provide will help you to do this, but not all queries are created equally.

    hashtag

    As a sub-section of "Using the wrong query" I want to talk about querying on the container directly.

    We want to ensure that your users can interact with your UI and if you query around using querySelector we lose a lot of that confidence, the test is harder to read, and it will break more frequently. This goes hand-in-hand with the next sub-section:

    hashtag

    As a sub-section of "Using the wrong query", I want to talk about why I recommend you query by the actual text (in the case of localization, I recommend the default locale), rather than using test IDs or other mechanisms everywhere.

    If you don't query by the actual text, then you have to do extra work to make sure that your translations are getting applied correctly. The biggest complaint I hear about this is that it leads to content writers breaking your tests. My rebuttal to that is that first, if a content writer changes "Username" to "Email" that's a change I definitely want to know about (because I'll need to change my implementation). Also, if there is a situation where they break something, fixing that issue takes no time at all. It's easy to triage and easy to fix.

    So the cost is pretty low, and the benefit is you get increased confidence that your translations are applied correctly and your tests are easier to write and read.

    I should mention that not everyone agrees with me on this, feel free to read more about it .

    hashtag

    As a sub-section of "Using the wrong query" I want to talk about *ByRole. In recent versions, the *ByRole queries have been seriously improved (primarily thanks to great work by ) and are now the number one recommended approach to query your component's output. Here are some of my favorite features.

    The name option allows you to query elements by their which is what screen readers will read for the element and it works even if your element has its text content split up by different elements. For example:

    One reason people don't use *ByRole queries is because they're not familiar with the implicit roles placed on elements. . So another one of my favorite features of the *ByRole queries is that if we're unable to find an element with the role you've specified, not only will we log the entire DOM to you like we do with normal get* or find* variants, but we also log all the available roles you can query by!

    This will fail with the following error message:

    Notice that we didn't have to add the role=button to our button for it to have the role of button. That's an implicit role, which leads us perfectly into our next one...

    Advice: Read and follow the recommendations .**

    hashtag

    Importance: high

    Slapping accessibility attributes willy nilly is not only unnecessary (as in the case above), but it can also confuse screen readers and their users. The accessibility attributes should really only be used when semantic HTML doesn't satisfy your use case (like if you're building a non-native UI that you want to make accessible ). If that's what you're building, be sure to use an existing library that does this accessibly or follow the WAI-ARIA practices. They often have .

    Note: to make inputs accessible via a "role" you'll want to specify the type attribute!

    Advice: Avoid adding unnecessary or incorrect accessibility attributes.

    hashtag

    Importance: medium

    is a package that's built on top of fireEvent, but it provides several methods that resemble the user interactions more closely. In the example above, fireEvent.change will simply trigger a single change event on the input. However the type call, will trigger keyDown, keyPress, and keyUp events for each character as well. It's much closer to the user's actual interactions. This has the benefit of working well with libraries that you may use which don't actually listen for the change event.

    We're still working on @testing-library/user-event to ensure that it delivers what it promises: firing all the same events the user would fire when performing a specific action. I don't think we're quite there yet and this is why it's not baked-into @testing-library/dom (though it may be at some point in the future). However, I'm confident enough in it to recommend you give it a look and use it's utilities over fireEvent.

    Advice: Use @testing-library/user-event over fireEvent where possible.

    hashtag

    Importance: high

    The only reason the query* variant of the queries is exposed is for you to have a function you can call which does not throw an error if no element is found to match the query (it returns null if no element is found). The only reason this is useful is to verify that an element is not rendered to the page. The reason this is so important is because the get* and find* variants will throw an extremely helpful error if no element is found–it prints out the whole document so you can see what's rendered and maybe why your query failed to find what you were looking for. Whereas query* will only return null and the best toBeInTheDocument can do is say: "null isn't in the document" which is not very helpful.

    Advice: Only use the query* variants for asserting that an element cannot be found.

    hashtag

    Importance: high

    Those two bits of code are basically equivalent (find* queries use waitFor under the hood), but the second is simpler and the error message you get will be better.

    Advice: use find* any time you want to query for something that may not be available right away.

    hashtag

    Importance: high

    The purpose of waitFor is to allow you to wait for a specific thing to happen. If you pass an empty callback it might work today because all you need to wait for is "one tick of the event loop" thanks to the way your mocks work. But you'll be left with a fragile test which could easily fail if you refactor your async logic.

    Advice: wait for a specific assertion inside waitFor.

    hashtag

    Importance: low

    Let's say that for the example above, window.fetch was called twice. So the waitFor call will fail, however, we'll have to wait for the timeout before we see that test failure. By putting a single assertion in there, we can both wait for the UI to settle to the state we want to assert on, and also fail faster if one of the assertions do end up failing.

    Advice: only put one assertion in a callback.

    hashtag

    Importance: high

    waitFor is intended for things that have a non-deterministic amount of time between the action you performed and the assertion passing. Because of this, the callback can be called (or checked for errors) a non-deterministic number of times and frequency (it's called both on an interval as well as when there are DOM mutations). So this means that your side-effect could run multiple times!

    This also means that you can't use snapshot assertions within waitFor. If you do want to use a snapshot assertion, then first wait for a specific assertion, and then after that you can take your snapshot.

    Advice: put side-effects outside waitFor callbacks and reserve the callback for assertions only.

    hashtag

    Importance: low

    This one's not really a big deal actually, but I thought I'd mention it and give my opinion on it. If get* queries are unsuccessful in finding the element, they'll throw a really helpful error message that shows you the full DOM structure (with syntax highlighting) which will help you during debugging. Because of this, the assertion could never possibly fail (because the query will throw before the assertion has a chance to).

    For this reason, many people skip the assertion. This really is fine honestly, but I personally normally keep the assertion in there just to communicate to readers of the code that it's not just an old query hanging around after a refactor but that I'm explicitly asserting that it exists.

    Advice: If you want to assert that something exists, make that assertion explicit.

    Accessibility in React

    hashtag
    Including keyboard usersarrow-up-right

    At this point, we've accomplished all of the features we set out to implement. A user can add a new task, check and uncheck tasks, delete tasks, or edit task names. And they can filter their task list by all, active, or completed tasks.

    Or, at least: they can do all of these things with a mouse. Unfortunately, these features are not very accessible to keyboard-only users. Let's explore this now.

    hashtag

    Start by clicking on the input at the top of our app, as if you're going to add a new task. You'll see a thick, dashed outline around that input. This outline is your visual indicator that the browser is currently focused on this element. Press the Tab key, and you will see the outline appear around the "Add" button beneath the input. This shows you that the browser's focus has moved.

    Press Tab a few more times, and you will see this dashed focus indicator move between each of the filter buttons. Keep going until the focus indicator is around the first "Edit" button. Press Enter.

    The <Todo /> component will switch templates, as we designed, and you'll see a form that lets us edit the name of the task.

    But where did our focus indicator go?

    When we switch between templates in our <Todo /> component, we completely remove the elements that were there before to replace them with something else. That means the element that we were focused on vanishes, and nothing is in focus at all. This could confuse a wide variety of users — particularly users who rely on the keyboard, or users who use a screen reader.

    To improve the experience for keyboard and screen-reader users, we should manage the browser's focus ourselves.

    hashtag

    When a user toggles a <Todo/> template from viewing to editing, we should focus on the <input> used to rename it; when they toggle back from editing to viewing, we should move focus back to the "Edit" button.

    hashtag

    In order to focus on an element in our DOM, we need to tell React which element we want to focus on and how to find it. React's hook creates an object with a single property: current. This property can be a reference to anything we want and look that reference up later. It's particularly useful for referring to DOM elements.

    Change the import statement at the top of Todo.js so that it includes useRef:

    Then, create two new constants beneath the hooks in your Todo() function. Each should be a ref – one for the "Edit" button in the view template and one for the edit field in the editing template.

    These refs have a default value of null because they will not have value until we attach them to their respective elements. To do that, we'll add an attribute of ref to each element, and set their values to the appropriately named ref objects.

    The textbox <input> in your editing template should be updated like this:

    The "Edit" button in your view template should read like this:

    hashtag

    To use our refs for their intended purpose, we need to import another React hook: . useEffect() is so named because it runs after React renders a given component, and will run any side-effects that we'd like to add to the render process, which we can't run inside the main function body. useEffect() is useful in the current situation because we cannot focus on an element until after the <Todo /> component renders and React knows where our refs are.

    Change the import statement of Todo.js again to add useEffect:

    useEffect() takes a function as an argument; this function is executed after the component renders. Let's see this in action; put the following useEffect() call just above the return statement in the body of Todo(), and pass into it a function that logs the words "side effect" to your console:

    To illustrate the difference between the main render process and code run inside useEffect(), add another log – put this one below the previous addition:

    Now, open the app in your browser. You should see both messages in your console, with each one repeating three times. Note how "main render" logged first, and "side effect" logged second, even though the "side effect" log appears first in the code.

    That's it for our experimentation for now. Delete console.log("main render") now, and lets move on to implementing our focus management.

    hashtag

    Now that we know our useEffect() hook works, we can manage focus with it. As a reminder, we want to focus on the editing field when we switch to the editing template.

    Update your existing useEffect() hook so that it reads like this:

    These changes make it so that, if isEditing is true, React reads the current value of the editFieldRef and moves browser focus to it. We also pass an array into useEffect() as a second argument. This array is a list of values useEffect() should depend on. With these values included, useEffect() will only run when one of those values changes. We only want to change focus when the value of isEditing changes.

    Try it now, and you'll see that when you click an "Edit" button, focus moves to the corresponding edit <input>!

    hashtag

    At first glance, getting React to move focus back to our "Edit" button when the edit is saved or cancelled appears deceptively easy. Surely we could add a condition to our useEffect to focus on the edit button if isEditing is false? Let's try it now — update your useEffect() call like so:

    This kind of mostly works. Head back to your browser and you'll see that your focus moves between Edit <input> and "Edit" button as you start and end an edit. However, you may have noticed a new problem — the "Edit" button in the final <Todo /> component is focussed immediately on page load, before we even interact with the app!

    Our useEffect() hook is behaving exactly as we designed it: it runs as soon as the component renders, sees that isEditing is false, and focuses the "Edit" button. Because there are three instances of <Todo />, we see focus on the last "Edit" button.

    We need to refactor our approach so that focus changes only when isEditing changes from one value to another.

    hashtag

    In order to meet our refined criteria, we need to know not just the value of isEditing, but also when that value has changed. In order to do that, we need to be able to read the previous value of the isEditing constant. Using pseudocode, our logic should be something like this:

    The React team had discussed , and has provided an example custom hook we can use for the job.

    Paste the following code near the top of Todo.js, above your Todo() function.

    Now we'll define a wasEditing constant beneath the hooks at the top of Todo(). We want this constant to track the previous value of isEditing, so we call usePrevious with isEditing as an argument:

    With this constant, we can update our useEffect() hook to implement the pseudocode we discussed before — update it as follows:

    Note that the logic of useEffect() now depends on wasEditing, so we provide it in the array of dependencies.

    Again try using the "Edit" and "Cancel" buttons to toggle between the templates of your <Todo /> component; you'll see the browser focus indicator move appropriately, without the problem we discussed at the start of this section.

    hashtag

    There's one last keyboard experience gap: when a user deletes a task from the list, the focus vanishes. We're going to follow a pattern similar to our previous changes: we'll make a new ref, and utilize our usePrevious() hook, so that we can focus on the list heading whenever a user deletes a task.

    hashtag

    Sometimes, the place we want to send our focus to is obvious: when we toggled our <Todo /> templates, we had an origin point to "go back" to — the "Edit" button. In this case however, since we're completely removing elements from the DOM, we have no place to go back to. The next best thing is an intuitive location somewhere nearby. The list heading is our best choice because it's close to the list item the user will delete, and focusing on it will tell the user how many tasks are left.

    hashtag

    Import the useRef() and useEffect() hooks into App.js — you'll need them both below:

    Then declare a new ref inside the App() function. Just above the return statement is a good place:

    hashtag

    Heading elements like our <h2> are not usually focusable. This isn't a problem — we can make any element programmatically focusable by adding the attribute to it. This means only focusable with JavaScript. You can't press Tab to focus on an element with a tabindex of -1 the same way you could do with a or element (this can be done using tabindex="0", but that's not really appropriate in this case).

    Let's add the tabindex attribute — written as tabIndex in JSX — to the heading above our list of tasks, along with our headingRef:

    Note: The tabindex attribute is great for accessibility edge-cases, but you should take great care to not overuse it. Only apply a tabindex to an element when you're absolutely sure that making it focusable will benefit your user in some way. In most cases, you should be utilizing elements that can naturally take focus, such as buttons, anchors, and inputs. Irresponsible usage of tabindex could have a profoundly negative impact on keyboard and screen-reader users!

    hashtag

    We want to focus on the element associated with our ref (via the ref attribute) only when our user deletes a task from their list. That's going to require the usePrevious() hook we already used earlier on. Add it to the top of your App.js file, just below the imports:

    Now add the following, above the return statement inside the App() function:

    Here we are invoking usePrevious() to track the length of the tasks state, like so:

    Note: Since we're now utilizing usePrevious() in two files, a good efficiency refactor would be to move the usePrevious() function into its own file, export it from that file, and import it where you need it. Try doing this as an exercise once you've got to the end.

    hashtag

    Now that we've stored how many tasks we previously had, we can set up a useEffect() hook to run when our number of tasks changes, which will focus the heading if the number of tasks we have now is less than with it previously was — i.e. we deleted a task!

    Add the following into the body of your App() function, just below your previous additions:

    We only try to focus on our list heading if we have fewer tasks now than we did before. The dependencies passed into this hook ensure it will only try to re-run when either of those values (the number of current tasks, or the number of previous tasks) changes.

    Now, when you delete a task in your browser, you will see our dashed focus outline appear around the heading above the list.

    ByPlaceholderText

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByPlaceholderText, queryByPlaceholderText, getAllByPlaceholderText, queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText

    hashtag
    API

    getByPlaceholderText(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      text: TextMatch,
      options?: {
        exact?: boolean = true,
        normalizer?: NormalizerFn,
      }): HTMLElement

    This will search for all elements with a placeholder attribute and find one that matches the given TextMatch.

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    Note

    A placeholder is not a good substitute for a label so you should generally use getByLabelText instead.

    hashtag
    Options

    TextMatch options

    Events and state

    With our component plan worked out, it's now time to start updating our app from a completely static UI to one that actually allows us to interact and change things. In this article we'll do this, digging into events and state along the way, and ending up with an app in which we can successfully add and delete tasks, and toggle tasks as completed.

    hashtag

    If you've only written vanilla JavaScript before now, you might be used to having a separate JavaScript file, where you query for some DOM nodes and attach listeners to them. For example:

    Team

    S.H.I.E.L.D.

    Scrum Board

    DNT Kanban Board

    Quick Filters:

    … Show more

    You have been disconnected from the estimation session. Reconnecting in 0 seconds. Try now.

    Please wait for a moderator to start.

    Participants

    Responsive Web Accessibility Review of Duke Energy Manual Audit for Duke Energy

    \

    Table of Contents

    Tailwind Cheatsheet

    chevron-rightTailwind Docs (DUKE)hashtag

    - Created by Anonymous, last modified on

    Tailwind CSS is a utility-based styling library. In order to streamline and standardize things like colors and spacing within our application, an Electron theme has been created to extend Tailwind's functionality, thus making it easy for us to access some standard Duke colors, fonts, etc. As a result, you will get most of the magic of Tailwind, but with most of the colors, text, and sizing options overwritten to reflect Duke's design aesthetic.

    We won't talk too much about the decisions behind why we are using this implementation here, but Chris Greufe has already done an incredible job documenting the ins-and-outs of it

    PureRenderMixin

    Note:

    PureRenderMixin is a legacy add-on. Use React.PureComponent instead.

    Importing

    hashtag

    CDN Links

    Both React and ReactDOM are available over a CDN.

    The versions above are only meant for development, and are not suitable for production. Minified and optimized production versions of React are available at:

    To load a specific version of react and react-dom, replace 17 with the version number.

    Add-Ons

    Note:

    React.addons entry point is deprecated as of React v15.5. The add-ons have moved to separate modules, and some of them have been deprecated.

    The React add-ons are a collection of useful utility modules for building React apps. These should be considered experimental and tend to change more often than the core.

    • createFragment

    Test Utilities

    Importing

    hashtag
    Overview

    ReactTestUtils makes it easy to test React components in the testing framework of your choice. At Facebook we use for painless JavaScript testing. Learn how to get started with Jest through the Jest website's .

    getByRole(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      role: TextMatch,
      options?: {
        exact?: boolean = true,
        hidden?: boolean = false,
        name?: TextMatch,
        description?: TextMatch,
        normalizer?: NormalizerFn,
        selected?: boolean,
        checked?: boolean,
        pressed?: boolean,
        current?: boolean | string,
        expanded?: boolean,
        queryFallbacks?: boolean,
        level?: number,
      }): HTMLElement

    Connie W.

    BA

    919-815-0512

    Luis R.

    Test

    JC Davila

    Test

    (521)811-244-9569

    Gisella T.

    Test

    Juan A.

    Test

    Smita K.

    Test

    414-581-6564

    Shashi V.

    Sitecore

    612-807-0325

    Franciso R.

    Sitecore

    (54 1)113-578-5282

    Villareal, Gibran

    FED

    (52 1)811-993-9079

    Bob Z.

    CI

    954-816-6673

    954-234-2759

    Cam A.

    CI

    828-514-1158

    Jill P.

    CI

    Guardians of the Galaxy

    Michelle R.

    PO

    980-329-1902

    Jayne H.

    SM

    704-576-1167

    Lesley J.

    SM

    980-221-4948

    Alba C.

    Test

    Julio C.

    Test

    Paul C.

    CI

    919-423-3335

    Trixie S.

    CI

    Juan A.

    Sitecore

    Federico R.

    Sitecore

    Watchmen

    Crystal H.

    PO

    704-564-5644

    Christa B.

    SM

    704-860-9109

    Chaz H.

    BA

    704-941-7170

    Paulo S.

    Test

    (52 1)818-257-5466

    Dilorom T.

    Test

    443-636-0765

    Alejandra V.

    Test

    (52 1) 811-411-7872

    Guillermo D.

    Sitecore

    (54 9)116-324-5902

    Nico

    Sitecore

    (54 9)113-418-1709

    Mike W.

    FED

    704-726-3405

    Bill S.

    FED

    Jay P.

    CI

    336-405-9706

    Sumedha A.

    CI

    936-204-1771

    Avengers

    Melissa P.

    PO

    704-807-8907

    Toni D.

    SM

    Sagar S.

    CI

    937-536-4282

    Sergio M.

    Sitecore

    (54 9) 221-505-4818

    Lesslie G.

    Test

    Franciso R.

    Test

    (52 1)844-442-7835

    X-Men

    Josh H.

    PO

    864-219-1489

    Liz W.

    SM

    704-918-5499

    704-807-4533

    Sarala P.

    Test

    704-550-8807

    Pam K.

    Test

    919-369-3287

    Kayal V.

    Test

    614-316-0702

    Louise C.

    FED

    704-999-9426

    Dilip J.

    Sitecore

    203-550-4242

    Damage Control

    Jonathan P.

    PO

    Scot H.

    Build

    Satish C.

    Build

    Alex K.

    Build

    Femi D.

    Build

    Stacey K.

    SM

    Amy B.

    Architect

    919-819-4471

    919-819-4471

    Inhumans (CI Team)

    Sagar S.

    937-536-4282

    Bob Z.

    954-816-6673

    Paul C.

    919-423-3335

    Anshul R.

    704-794-3909

    Sanjay R

    704-451-7702

    Joy V.

    704-906-5665

    Viswa J.

    704-606-3223

    Others Members & Support

    Karina G.

    Test Lead

    Sparks, Carlton

    Test Manager

    704-219-5146

    Vaibhav S.

    Prod Support

    980-474-0974

    Nikhil D.

    Prod Support

    909-553-2052

    Adi G.

    CI Deployment

    704-408-0500

    Terry A.

    CI Deployment

    704-517-8689

    Femi D.

    CI Deployment

    512-920-7275

    Courtney I.

    Content Team (Login Boxes)

    Jamie Martinez

    Customer Connect

    Dan Wright

    Customer Connect

    Kojo Fletcher

    Customer Connect

    217-220-8055

    Taryn Y.

    DEC Tester

    Angie Doerman

    DEMW Tester

    Connie W.

    DEP Tester

    Linda J.

    DEF Test

    727-580-1637

    Amy P.

    Legacy Test Liaison

    704-490-0523

    John H.

    DBA

    732-500-5522

    Cynthia J.

    RTE

    704-340-0444

    Heather M.

    Program Manager

    704-650-0166

    Vaibhav S. (Retail Supp)

    Prod Support

    980-474-0974

    Tonya G.

    PO

    704-807-8831

    Skip H.

    SM

    704-877-7547

    https://dewap.duke-energy.com/EHSTrain/HS1010_WS/index_lms.html?aicc_sid=AICChwSV3HVHAqqYgW73-UGijiVHXrT-ZrNFx8YJQvzf5jI&aicc_url=https%3A%2F%2Fduke-energy.csod.com%2FLMS%2Fscorm%2Faicc.aspxdewap.duke-energy.comchevron-right
    https://app.abstract.com/projects/7d33aa49-f1f0-47eb-971d-893d6457bcbcapp.abstract.comchevron-right
    https://dewap.duke-energy.com/EHSTrain/HS1010_WS/index_lms.html?aicc_sid=AICChwSV3HVHAqqYgW73-UGijiVHXrT-ZrNFx8YJQvzf5jI&aicc_url=https%3A%2F%2Fduke-energy.csod.com%2FLMS%2Fscorm%2Faicc.aspxdewap.duke-energy.comchevron-right
    https://app.abstract.com/projects/7d33aa49-f1f0-47eb-971d-893d6457bcbcapp.abstract.comchevron-right
    could not be loaded. Whether you want to assert this functionality in your test or not is up to you.
    a table of HTML elements with their default and desired rolesarrow-up-right
    accessible name or descriptionarrow-up-right
    "What is an accessible name?" from ThePacielloGrouparrow-up-right
    ARIA aria-selectedarrow-up-right
    ARIA aria-checkedarrow-up-right
    herearrow-up-right
    ARIA aria-currentarrow-up-right
    ARIA aria-pressedarrow-up-right
    ARIA aria-expandedarrow-up-right
    ARIA aria-levelarrow-up-right
    accessible descriptionarrow-up-right
    alertdialogarrow-up-right
    belowarrow-up-right
    #169arrow-up-right
    Cypress.ioarrow-up-right
    docblocksarrow-up-right
    docblocksarrow-up-right
    cross-envarrow-up-right
    root hookarrow-up-right
    eslint-plugin-testing-libraryarrow-up-right
    eslint-plugin-jest-domarrow-up-right
    Using wrapper as the variable name for the return value from renderarrow-up-right
    Using cleanuparrow-up-right
    Learn morearrow-up-right
    Not using screenarrow-up-right
    was added in DOM Testing Library v6.11.0arrow-up-right
    screen.debugarrow-up-right
    Using the wrong assertionarrow-up-right
    jest-domarrow-up-right
    @testing-library/jest-domarrow-up-right
    Wrapping things in act unnecessarilyarrow-up-right
    Fix the "not wrapped in act(...)" warningarrow-up-right
    Using the wrong queryarrow-up-right
    "Which query should I use?"arrow-up-right
    Using container to query for elementsarrow-up-right
    Not querying by textarrow-up-right
    in this tweet threadarrow-up-right
    Not using *ByRole most of the timearrow-up-right
    Sebastian Silbermannarrow-up-right
    "Accessible Name"arrow-up-right
    Here's a list of Roles on MDNarrow-up-right
    The "Which Query Should I Use" Guidearrow-up-right
    Adding aria-, role, and other accessibility attributes incorrectlyarrow-up-right
    like an autocompletearrow-up-right
    great examplesarrow-up-right
    Not using @testing-library/user-eventarrow-up-right
    @testing-library/user-eventarrow-up-right
    Using query* variants for anything except checking for non-existencearrow-up-right
    Using waitFor to wait for elements that can be queried with find*arrow-up-right
    Passing an empty callback to waitForarrow-up-right
    Having multiple assertions in a single waitFor callbackarrow-up-right
    Performing side-effects in waitForarrow-up-right
    Using get* variants as assertionsarrow-up-right
    Exploring the keyboard usability problemarrow-up-right
    Focusing between templatesarrow-up-right
    Targeting our elementsarrow-up-right
    useRefarrow-up-right
    Focusing on our refs with useEffectarrow-up-right
    useEffect()arrow-up-right
    Focusing on our editing fieldarrow-up-right
    Moving focus back to the edit buttonarrow-up-right
    More robust focus managementarrow-up-right
    ways to get a component's previous statearrow-up-right
    Focusing when the user deletes a taskarrow-up-right
    Why the list heading?arrow-up-right
    Creating our refarrow-up-right
    Prepare the headingarrow-up-right
    tabindex="-1"arrow-up-right
    <button>arrow-up-right
    <a>arrow-up-right
    Getting previous statearrow-up-right
    Using useEffect() to control our heading focusarrow-up-right
    <input placeholder="Username" />
    import { screen } from "@testing-library/dom";
    
    const inputNode = screen.getByPlaceholderText("Username");
    Overview

    If your React component's render function renders the same result given the same props and state, you can use this mixin for a performance boost in some cases.

    Example:

    Under the hood, the mixin implements shouldComponentUpdate, in which it compares the current props and state with the next ones and returns false if the equalities pass.

    Note:

    This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objectsarrow-up-right to facilitate fast comparisons of nested data.

    Furthermore, shouldComponentUpdate skips updates for the whole component subtree. Make sure all the children components are also "pure".

    import PureRenderMixin from "react-addons-pure-render-mixin"; // ES6
    var PureRenderMixin = require("react-addons-pure-render-mixin"); // ES5 with npm
    hashtag
    Why the crossorigin Attribute?

    If you serve React from a CDN, we recommend to keep the crossoriginarrow-up-right attribute set:

    We also recommend to verify that the CDN you are using sets the Access-Control-Allow-Origin: * HTTP header:

    This enables a better error handling experience in React 16 and later.

    <script
      crossorigin
      src="https://unpkg.com/react@17/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react@17/umd/react.production.min.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"
    ></script>
    <body>
      <main aria-hidden="true">
        <button>Open dialog</button>
      </main>
      <div role="dialog">
        <button>Close dialog</button>
      </div>
    </body>
    <body>
      <div role="tablist">
        <button role="tab" aria-selected="true">Native</button>
        <button role="tab" aria-selected="false">React</button>
        <button role="tab" aria-selected="false">Cypress</button>
      </div>
    </body>
    <body>
      <section>
        <button role="checkbox" aria-checked="true">Sugar</button>
        <button role="checkbox" aria-checked="false">Gummy bears</button>
        <button role="checkbox" aria-checked="false">Whipped cream</button>
      </section>
    </body>
    <body>
      <nav>
        <a href="current/page" aria-current="true">👍</a>
        <a href="another/page">👎</a>
      </nav>
    </body>
    <body>
      <section>
        <button aria-pressed="true">👍</button>
        <button aria-pressed="false">👎</button>
      </section>
    </body>
    <body>
      <nav>
        <ul>
          <li>
            <a aria-expanded="false" aria-haspopup="true" href="..."
              >Expandable Menu Item</a
            >
            <ul>
              <li><a href="#">Submenu Item 1</a></li>
              <li><a href="#">Submenu Item 1</a></li>
            </ul>
          </li>
          <li><a href="#">Regular Menu Item</a></li>
        </ul>
      </nav>
    </body>
    <div role="dialog">...</div>
    import { screen } from "@testing-library/dom";
    
    const dialogContainer = screen.getByRole("dialog");
    import { render, screen } from "@testing-library/react";
    
    render(<MyComponent />);
    const dialogContainer = screen.getByRole("dialog");
    cy.findByRole("dialog").should("exist");
    <body>
      <section>
        <h1>Heading Level One</h1>
        <h2>First Heading Level Two</h2>
        <h3>Heading Level Three</h3>
        <div role="heading" aria-level="2">Second Heading Level Two</div>
      </section>
    </body>
    getByRole("heading", { level: 1 });
    // <h1>Heading Level One</h1>
    
    getAllByRole("heading", { level: 2 });
    // [
    //   <h2>First Heading Level Two</h2>,
    //   <div role="heading" aria-level="2">Second Heading Level Two</div>
    // ]
    <body>
      <ul>
        <li role="alertdialog" aria-describedby="notification-id-1">
          <div><button>Close</button></div>
          <div id="notification-id-1">You have unread emails</div>
        </li>
        <li role="alertdialog" aria-describedby="notification-id-2">
          <div><button>Close</button></div>
          <div id="notification-id-2">Your session is about to expire</div>
        </li>
      </ul>
    </body>
    getByRole("alertdialog", { description: "Your session is about to expire" });
    - import { render, fireEvent } from '@testing-library/react';
    + import { render, fireEvent } from '../test-utils';
    import React from "react";
    import { render } from "@testing-library/react";
    import { ThemeProvider } from "my-ui-lib";
    import { TranslationProvider } from "my-i18n-lib";
    import defaultStrings from "i18n/en-x-default";
    
    const AllTheProviders = ({ children }) => {
      return (
        <ThemeProvider theme="light">
          <TranslationProvider messages={defaultStrings}>
            {children}
          </TranslationProvider>
        </ThemeProvider>
      );
    };
    
    const customRender = (ui, options) =>
      render(ui, { wrapper: AllTheProviders, ...options });
    
    // re-export everything
    export * from "@testing-library/react";
    
    // override render method
    export { customRender as render };
    - import { render, fireEvent } from '@testing-library/react';
    + import { render, fireEvent } from '../test-utils';
    import React, { FC, ReactElement } from "react";
    import { render, RenderOptions } from "@testing-library/react";
    import { ThemeProvider } from "my-ui-lib";
    import { TranslationProvider } from "my-i18n-lib";
    import defaultStrings from "i18n/en-x-default";
    
    const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => {
      return (
        <ThemeProvider theme="light">
          <TranslationProvider messages={defaultStrings}>
            {children}
          </TranslationProvider>
        </ThemeProvider>
      );
    };
    
    const customRender = (
      ui: ReactElement,
      options?: Omit<RenderOptions, "wrapper">
    ) => render(ui, { wrapper: AllTheProviders, ...options });
    
    export * from "@testing-library/react";
    export { customRender as render };
    const rtl = require("@testing-library/react");
    
    const customRender = (ui, options) =>
      rtl.render(ui, {
        myDefaultOption: "something",
        ...options,
      });
    
    module.exports = {
      ...rtl,
      render: customRender,
    };
    import { queryHelpers, buildQueries } from "@testing-library/react";
    
    // The queryAllByAttribute is a shortcut for attribute-based matchers
    // You can also use document.querySelector or a combination of existing
    // testing library utilities to find matching nodes for your query
    const queryAllByDataCy = (...args) =>
      queryHelpers.queryAllByAttribute("data-cy", ...args);
    
    const getMultipleError = (c, dataCyValue) =>
      `Found multiple elements with the data-cy attribute of: ${dataCyValue}`;
    const getMissingError = (c, dataCyValue) =>
      `Unable to find an element with the data-cy attribute of: ${dataCyValue}`;
    
    const [
      queryByDataCy,
      getAllByDataCy,
      getByDataCy,
      findAllByDataCy,
      findByDataCy,
    ] = buildQueries(queryAllByDataCy, getMultipleError, getMissingError);
    
    export {
      queryByDataCy,
      queryAllByDataCy,
      getByDataCy,
      getAllByDataCy,
      findAllByDataCy,
      findByDataCy,
    };
    import { render, queries } from "@testing-library/react";
    import * as customQueries from "./custom-queries";
    
    const customRender = (ui, options) =>
      render(ui, { queries: { ...queries, ...customQueries }, ...options });
    
    // re-export everything
    export * from "@testing-library/react";
    
    // override render method
    export { customRender as render };
    import { render, queries, RenderOptions } from "@testing-library/react";
    import * as customQueries from "./custom-queries";
    import { ReactElement } from "react";
    
    const customRender = (
      ui: ReactElement,
      options?: Omit<RenderOptions, "queries">
    ) => render(ui, { queries: { ...queries, ...customQueries }, ...options });
    
    export * from "@testing-library/react";
    export { customRender as render };
    const { getByDataCy } = render(<Component />);
    
    expect(getByDataCy("my-component")).toHaveTextContent("Hello");
    - import { render, fireEvent } from '../test-utils';
    + import { render, fireEvent } from 'test-utils';
    module.exports = {
      moduleDirectories: [
        'node_modules',
    +   // add the directory with the test-utils.js file, for example:
    +   'utils', // a utility folder
    +    __dirname, // the root directory
      ],
      // ... other options ...
    }
    {
      "compilerOptions": {
        "baseUrl": "src",
        "paths": {
          "test-utils": ["./utils/test-utils"]
        }
      }
    }
    npm install --save-dev jest-environment-jsdom
     module.exports = {
    +  testEnvironment: 'jsdom',
       // ... other options ...
     }
    /**
     * @jest-environment jsdom
     */
     module.exports = {
    +  testEnvironment: 'jest-environment-jsdom',
       // ... other options ...
     }
    /**
     * @jest-environment jsdom
     */
    npm install --save-dev jest-environment-jsdom-fifteen
     module.exports = {
    +  testEnvironment: 'jest-environment-jsdom-fifteen',
       // ... other options ...
     }
    npm install --save-dev jsdom global-jsdom
    mocha --require global-jsdom/register
    cross-env RTL_SKIP_AUTO_CLEANUP=true jest
    {
      // ... other jest config
      setupFiles: ["@testing-library/react/dont-cleanup-after-each"];
    }
    mocha -r @testing-library/react/dont-cleanup-after-each
    const { cleanup } = require("@testing-library/react");
    
    exports.mochaHooks = {
      afterEach() {
        cleanup();
      },
    };
    mocha -r ./mocha-watch-cleanup-after-each.js
    // ❌
    const wrapper = render(<Example prop="1" />)
    wrapper.rerender(<Example prop="2" />)
    
    // ✅
    const {rerender} = render(<Example prop="1" />)
    rerender(<Example prop="2" />)
    // ❌
    import {render, screen, cleanup} from '@testing-library/react'
    
    afterEach(cleanup)
    
    // ✅
    import {render, screen} from '@testing-library/react'
    // ❌
    const {getByRole} = render(<Example />)
    const errorMessageNode = getByRole('alert')
    
    // ✅
    render(<Example />)
    const errorMessageNode = screen.getByRole('alert')
    import {render, screen} from '@testing-library/react'
    const button = screen.getByRole('button', {name: /disabled button/i})
    
    // ❌
    expect(button.disabled).toBe(true)
    // error message:
    //  expect(received).toBe(expected) // Object.is equality
    //
    //  Expected: true
    //  Received: false
    
    // ✅
    expect(button).toBeDisabled()
    // error message:
    //   Received element is not disabled:
    //     <button />
    // ❌
    act(() => {
      render(<Example />)
    })
    
    const input = screen.getByRole('textbox', {name: /choose a fruit/i})
    act(() => {
      fireEvent.keyDown(input, {key: 'ArrowDown'})
    })
    
    // ✅
    render(<Example />)
    const input = screen.getByRole('textbox', {name: /choose a fruit/i})
    fireEvent.keyDown(input, {key: 'ArrowDown'})
    // ❌
    // assuming you've got this DOM to work with:
    // <label>Username</label><input data-testid="username" />
    screen.getByTestId('username')
    
    // ✅
    // change the DOM to be accessible by associating the label and setting the type
    // <label for="username">Username</label><input id="username" type="text" />
    screen.getByRole('textbox', {name: /username/i})
    // ❌
    const {container} = render(<Example />)
    const button = container.querySelector('.btn-primary')
    expect(button).toHaveTextContent(/click me/i)
    
    // ✅
    render(<Example />)
    screen.getByRole('button', {name: /click me/i})
    // ❌
    screen.getByTestId('submit-button')
    
    // ✅
    screen.getByRole('button', {name: /submit/i})
    // assuming we've got this DOM structure to work with
    // <button><span>Hello</span> <span>World</span></button>
    
    screen.getByText(/hello world/i)
    // ❌ fails with the following error:
    // Unable to find an element with the text: /hello world/i. This could be
    // because the text is broken up by multiple elements. In this case, you can
    // provide a function for your text matcher to make your matcher more flexible.
    
    screen.getByRole('button', {name: /hello world/i})
    // ✅ works!
    // assuming we've got this DOM structure to work with
    // <button><span>Hello</span> <span>World</span></button>
    screen.getByRole('blah')
    TestingLibraryElementError: Unable to find an accessible element with the role "blah"
    
    Here are the accessible roles:
    
      button:
    
      Name "Hello World":
      <button />
    
      --------------------------------------------------
    
    <body>
      <div>
        <button>
          <span>
            Hello
          </span>
    
          <span>
            World
          </span>
        </button>
      </div>
    </body>
    // ❌
    render(<button role="button">Click me</button>)
    
    // ✅
    render(<button>Click me</button>)
    // ❌
    fireEvent.change(input, {target: {value: 'hello world'}})
    
    // ✅
    userEvent.type(input, 'hello world')
    // ❌
    expect(screen.queryByRole('alert')).toBeInTheDocument()
    
    // ✅
    expect(screen.getByRole('alert')).toBeInTheDocument()
    expect(screen.queryByRole('alert')).not.toBeInTheDocument()
    // ❌
    const submitButton = await waitFor(() =>
      screen.getByRole('button', {name: /submit/i}),
    )
    
    // ✅
    const submitButton = await screen.findByRole('button', {name: /submit/i})
    // ❌
    await waitFor(() => {})
    expect(window.fetch).toHaveBeenCalledWith('foo')
    expect(window.fetch).toHaveBeenCalledTimes(1)
    
    // ✅
    await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
    expect(window.fetch).toHaveBeenCalledTimes(1)
    // ❌
    await waitFor(() => {
      expect(window.fetch).toHaveBeenCalledWith('foo')
      expect(window.fetch).toHaveBeenCalledTimes(1)
    })
    
    // ✅
    await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
    expect(window.fetch).toHaveBeenCalledTimes(1)
    // ❌
    await waitFor(() => {
      fireEvent.keyDown(input, {key: 'ArrowDown'})
      expect(screen.getAllByRole('listitem')).toHaveLength(3)
    })
    
    // ✅
    fireEvent.keyDown(input, {key: 'ArrowDown'})
    await waitFor(() => {
      expect(screen.getAllByRole('listitem')).toHaveLength(3)
    })
    // ❌
    screen.getByRole('alert', {name: /error/i})
    
    // ✅
    expect(screen.getByRole('alert', {name: /error/i})).toBeInTheDocument()
    import React, { useRef, useState } from "react";
    const editFieldRef = useRef(null);
    const editButtonRef = useRef(null);
    <input
      id={props.id}
      className="todo-text"
      type="text"
      value={newName}
      onChange={handleChange}
      ref={editFieldRef}
    />
    <button
      type="button"
      className="btn"
      onClick={() => setEditing(true)}
      ref={editButtonRef}
    >
      Edit <span className="visually-hidden">{props.name}</span>
    </button>
    import React, { useEffect, useRef, useState } from "react";
    useEffect(() => {
      console.log("side effect");
    });
    console.log("main render");
    main render (3)                                     Todo.js:100
    side effect (3)                                     Todo.js:98
    useEffect(() => {
      if (isEditing) {
        editFieldRef.current.focus();
      }
    }, [isEditing]);
    useEffect(() => {
      if (isEditing) {
        editFieldRef.current.focus();
      } else {
        editButtonRef.current.focus();
      }
    }, [isEditing]);
    if (wasNotEditingBefore && isEditingNow) {
      focusOnEditField()
    }
    if (wasEditingBefore && isNotEditingNow) {
      focusOnEditButton()
    }
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    }
    const wasEditing = usePrevious(isEditing);
    useEffect(() => {
      if (!wasEditing && isEditing) {
        editFieldRef.current.focus();
      }
      if (wasEditing && !isEditing) {
        editButtonRef.current.focus();
      }
    }, [wasEditing, isEditing]);
    import React, { useState, useRef, useEffect } from "react";
    const listHeadingRef = useRef(null);
    <h2 id="list-heading" tabIndex="-1" ref={listHeadingRef}>
      {headingText}
    </h2>
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    }
    const prevTaskLength = usePrevious(tasks.length);
    useEffect(() => {
      if (tasks.length - prevTaskLength === -1) {
        listHeadingRef.current.focus();
      }
    }, [tasks.length, prevTaskLength]);
    import { render, screen } from "@testing-library/react";
    
    render(<MyComponent />);
    const inputNode = screen.getByPlaceholderText("Username");
    cy.findByPlaceholderText("Username").should("exist");
    const createReactClass = require("create-react-class");
    
    createReactClass({
      mixins: [PureRenderMixin],
    
      render: function () {
        return <div className={this.props.className}>foo</div>;
      },
    });
    <script crossorigin src="..."></script>
    In React, we write event handlers directly on the elements in our JSX, like this:

    Note: This may seem counter-intuitive regarding best-practice advice that tends to advise against use of inline event handlers on HTML, but remember that JSX is actually part of your JavaScript.

    In the above example, we're adding an onClick attribute to the <button> element. The value of that attribute is a function that triggers a simple alert.

    The onClick attribute has special meaning here: it tells React to run a given function when the user clicks on the button. There are a couple of other things to note:

    • The camel-cased nature of onClick is important — JSX will not recognize onclick (again, it is already used in JavaScript for a specific purpose, which is related but different — standard onclickarrow-up-right handler properties).

    • All browser events follow this format in JSX – on, followed by the name of the event.

    Let's apply this to our app, starting in the Form.js component.

    hashtag
    Handling form submissionarrow-up-right

    At the top of the Form() component function, create a function named handleSubmit(). This function should prevent the default behavior of the submit eventarrow-up-right. After that, it should trigger an alert(), which can say whatever you'd like. It should end up looking something like this:

    To use this function, add an onSubmit attribute to the <form>arrow-up-right element, and set its value to the handleSubmit function:

    Now if you head back to your browser and click on the "Add" button, your browser will show you an alert dialog with the words "Hello, world!" — or whatever you chose to write there.

    hashtag
    Callback propsarrow-up-right

    In React applications, interactivity is rarely confined to just one component: events that happen in one component will affect other parts of the app. When we start giving ourselves the power to make new tasks, things that happen in the <Form /> component will affect the list rendered in <App />.

    We want our handleSubmit() function to ultimately help us create a new task, so we need a way to pass information from <Form /> to <App />. We can't pass data from child to parent in the same way as we pass data from parent to child using standard props. Instead, we can write a function in <App /> that will expect some data from our form as an input, then pass that function to <Form /> as a prop. This function-as-a-prop is called a callback prop. Once we have our callback prop, we can call it inside <Form /> to send the right data to <App />.

    hashtag
    Handling form submission via callbacksarrow-up-right

    Inside the top of our App() component function, create a function named addTask() which has a single parameter of name:

    Next, we'll pass addTask() into <Form /> as a prop. The prop can have whatever name you want, but pick a name you'll understand later. Something like addTask works, because it matches the name of the function as well as what the function will do. Your <Form /> component call should be updated as follows:

    Finally, you can use this prop inside the handleSubmit() function in your <Form /> component! Update it as follows:

    Clicking on the "Add" button in your browser will prove that the addTask() callback function works, but it'd be nice if we could get the alert to show us what we're typing in our input field! This is what we'll do next.

    Note: We decided to name our callback prop addTask to make it easy to understand what the prop will do. Another common convention you may well come across in React code is to prefix callback prop names with the word on, followed by the name of the event that will cause them to be run. For instance, we could have given our form a prop of onSubmit with the value of addTask.

    hashtag
    State and the useState hookarrow-up-right

    So far, we've used props to pass data through our components and this has served us just fine. Now that we're dealing with user input and data updates, however, we need something more.

    For one thing, props come from the parent of a component. Our <Form /> will not be inheriting a new name for our task; our <input /> element lives directly inside of <Form />, so <Form/> will be directly responsible for creating that new name. We can't ask <Form /> to spontaneously create its own props, but we can ask it to track some of its own data for us. Data such as this, which a component itself owns, is called state. State is another powerful tool for React because components not only own state, but can update it later. It's not possible to update the props a component receives; only to read them.

    React provides a variety of special functions that allow us to provide new capabilities to components, like state. These functions are called hooks, and the useState hook, as its name implies, is precisely the one we need in order to give our component some state.

    To use a React hook, we need to import it from the react module. In Form.js, change your very first line so that it reads like this:

    This allows us to import the useState() function by itself, and utilize it anywhere in this file.

    useState() creates a piece of state for a component, and its only parameter determines the initial value of that state. It returns two things: the state, and a function that can be used to update the state later.

    This is a lot to take in at once, so let's try it out. We're going to make ourselves a name state, and a function for updating the name state.

    Write the following above your handleSubmit() function, inside Form():

    What's going on in this line of code?

    • We are setting the initial name value as "Use hooks!".

    • We are defining a function whose job is to modify name, called setName().

    • useState() returns these two things, so we are using to capture them both in separate variables.

    hashtag
    Reading statearrow-up-right

    You can see the name state in action right away. Add a value attribute to the form's input, and set its value to name. Your browser will render "Use hooks!" inside the input.

    Change "Use hooks!" to an empty string once you're done; this is what we want for our initial state.

    hashtag
    Reading user inputarrow-up-right

    Before we can change the value of name, we need to capture a user's input as they type. For this, we can listen to the onChange event. Let's write a handleChange() function, and listen for it on the <input /> tag.

    Currently, your input's value will not change as you type, but your browser will log the word "Typing!" to the JavaScript console, so we know our event listener is attached to the input. In order to change the input's value, we have to use our handleChange() function to update our name state.

    To read the contents of the input field as they change, you can access the input's value property. We can do this inside handleChange() by reading e.target.value. e.target represents the element that fired the change event — that's our input. So, value is the text inside it.

    You can console.log() this value to see it in your browser's console.

    hashtag
    Updating statearrow-up-right

    Logging isn't enough — we want to actually store the updated state of the name as the input value changes! Change the console.log() to setName(), as shown below:

    Now we need to change our handleSubmit() function so that it calls props.addTask with name as an argument — remember our callback prop? This will serve to send the task back to the App component, so we can add it to our list of tasks at some later date. As a matter of good practice, you should clear the input after your form submits, so we'll call setName() again with an empty string to do so:

    At last, you can type something into the input field in your browser and click Add — whatever you typed will appear in an alert dialog.

    Your Form.js file should now read like this:

    Note: One thing you'll notice is that you are able to submit empty tasks by just pressing the Add button without entering a task name. Can you think of a way to disallow empty tasks from being added? As a hint, you probably need to add some kind of check into the handleSubmit() function.

    hashtag
    Putting it all together: Adding a taskarrow-up-right

    Now that we've practiced with events, callback props, and hooks we're ready to write functionality that will allow a user to add a new task from their browser.

    hashtag
    Tasks as statearrow-up-right

    Import useState into App.js, so that we can store our tasks in state — update your React import line to the following:

    We want to pass props.tasks into the useState() hook – this will preserve its initial state. Add the following right at the top of your App() function definition:

    Now, we can change our taskList mapping so that it is the result of mapping tasks, instead of props.tasks. Your taskList constant declaration should now look like so:

    hashtag
    Adding a taskarrow-up-right

    We've now got a setTasks hook that we can use in our addTask() function to update our list of tasks. There's one problem however: we can't just pass the name argument of addTask() into setTasks, because tasks is an array of objects and name is a string. If we tried to do this, the array would be replaced with the string.

    First of all, we need to put name into an object that has the same structure as our existing tasks. Inside of the addTask() function, we will make a newTask object to add to the array.

    We then need to make a new array with this new task added to it and then update the state of the tasks data to this new state. To do this, we can use spread syntax to copy the existing arrayarrow-up-right, and add our object at the end. We then pass this array into setTasks() to update the state.

    Putting that all together, your addTask() function should read like so:

    Now you can use the browser to add a task to our data! Type anything into the form and click "Add" (or press the Enter key) and you'll see your new todo item appear in the UI!

    However, we have another problem: our addTask() function is giving each task the same id. This is bad for accessibility, and makes it impossible for React to tell future tasks apart with the key prop. In fact, React will give you a warning in your DevTools console — "Warning: Encountered two children with the same key..."

    We need to fix this. Making unique identifiers is a hard problem – one for which the JavaScript community has written some helpful libraries. We'll use nanoidarrow-up-right because it's tiny, and it works.

    Make sure you're in the root directory of your application and run the following terminal command:

    Note: If you're using yarn, you'll need the following instead: yarn add nanoid

    Now we can import nanoid into the top of App.js so we can use it to create unique IDs for our new tasks. First of all, include the following import line at the top of App.js:

    Now let's update addTask() so that each task ID becomes a prefix todo- plus a unique string generated by nanoid. Update your newTask constant declaration to this:

    Save everything, and try your app again — now you can add tasks without getting that warning about duplicate IDs.

    hashtag
    Detour: counting tasksarrow-up-right

    Now that we can add new tasks, you may notice a problem: our heading reads 3 tasks remaining, no matter how many tasks we have! We can fix this by counting the length of taskList and changing the text of our heading accordingly.

    Add this inside your App() definition, before the return statement:

    Hrm. This is almost right, except that if our list ever contains a single task, the heading will still use the word "tasks". We can make this a variable, too. Update the code you just added as follows:

    Now you can replace the list heading's text content with the headingText variable. Update your <h2> like so:

    hashtag
    Completing a taskarrow-up-right

    You might notice that, when you click on a checkbox, it checks and unchecks appropriately. As a feature of HTML, the browser knows how to remember which checkbox inputs are checked or unchecked without our help. This feature hides a problem, however: toggling a checkbox doesn't change the state in our React application. This means that the browser and our app are now out-of-sync. We have to write our own code to put the browser back in sync with our app.

    hashtag
    Proving the bugarrow-up-right

    Before we fix the problem, let's observe it happening.

    We'll start by writing a toggleTaskCompleted() function in our App() component. This function will have an id parameter, but we're not going to use it yet. For now, we'll log the first task in the array to the console – we're going to inspect what happens when we check or uncheck it in our browser:

    Add this just above your taskList constant declaration:

    Next, we'll add toggleTaskCompleted to the props of each <Todo /> component rendered inside our taskList; update it like so:

    Next, go over to your Todo.js component and add an onChange handler to your <input /> element, which should use an anonymous function to call props.toggleTaskCompleted() with a parameter of props.id. The <input /> should now look like this:

    Save everything and return to your browser and notice that our first task, Eat, is checked. Open your JavaScript console, then click on the checkbox next to Eat. It unchecks, as we expect. Your JavaScript console, however, will log something like this:

    The checkbox unchecks in the browser, but our console tells us that Eat is still completed. We will fix that next!

    hashtag
    Synchronizing the browser with our dataarrow-up-right

    Let's revisit our toggleTaskCompleted() function in App.js. We want it to change the completed property of only the task that was toggled, and leave all the others alone. To do this, we'll map() over the task list and just change the one we completed.

    Update your toggleTaskCompleted() function to the following:

    Here, we define an updatedTasks constant that maps over the original tasks array. If the task's id property matches the id provided to the function, we use object spread syntaxarrow-up-right to create a new object, and toggle the checked property of that object before returning it. If it doesn't match, we return the original object.

    Then we call setTasks() with this new array in order to update our state.

    hashtag
    Deleting a taskarrow-up-right

    Deleting a task will follow a similar pattern to toggling its completed state: We need to define a function for updating our state, then pass that function into <Todo /> as a prop and call it when the right event happens.

    hashtag
    The deleteTask callback proparrow-up-right

    Here we'll start by writing a deleteTask() function in your App component. Like toggleTaskCompleted(), this function will take an id parameter, and we will log that id to the console to start with. Add the following below toggleTaskCompleted():

    Next, add another callback prop to our array of <Todo /> components:

    In Todo.js, we want to call props.deleteTask() when the "Delete" button is pressed. deleteTask() needs to know the ID of the task that called it, so it can delete the correct task from the state.

    Update the "Delete" button inside Todo.js, like so:

    Now when you click on any of the "Delete" buttons in the app, your browser console should log the ID of the related task.

    hashtag
    Deleting tasks from state and UIarrow-up-right

    Now that we know deleteTask() is invoked correctly, we can call our setTasks() hook in deleteTask() to actually delete that task from the app's state as well as visually in the app UI. Since setTasks() expects an array as an argument, we should provide it with a new array that copies the existing tasks, excluding the task whose ID matches the one passed into deleteTask().

    This is a perfect opportunity to use Array.prototype.filter()arrow-up-right. We can test each task, and exclude a task from the new array if its id prop matches the id parameter passed into deleteTask().

    Update the deleteTask() function inside your App.js file as follows:

    Try your app out again. Now you should be able to delete a task from your app!

    Handling eventsarrow-up-right

    Show estimates Restart

    Support

    • Backlog

      92

    • Selected for Development

      10

    • In Progress

      26

    • Testing

      3

    • Release…

      Done

      14 of 2108

    Expedite1 issue

    • DNT-1102arrow-up-right

      Refactor DE.com Homepage to Use JSS (Residential)

    Everything Else144 issues

    • DNT-2577Secondary Nav Updates

      DNT-2609arrow-up-right

      Testing

      DNT-2636a11y audit - Nav Cards

      DNT-2679arrow-up-right

      Execute test cases

      DNT-2639a11y audit - Icon List

      Execute test cases

      DNT-2689a11y audit - NewsBanner

      Execute test cases

      DNT-2690a11y audit - Search

      Execute test cases

      DNT-2807a11y audit - NewsAndResources

      Execute test cases

      DNT-2814a11y audit - GlobalAlert

      Execute test cases

      DNT-2226Component Conversion: Additional Resources

      FED

      Send _ga cookie in DESSA Request

      Open Prod Issues / Hypercare

      R8 Hypercare - PROD - Sitecore - Need additional datalayer push on successful sign in from /home

      Open Prod Issues / Hypercare

      Water Access Alert content connection to Lake View app broken

      Open Prod Issues / Hypercare

      scprod-cms all iframes throwing component error

      Open Prod Issues / Hypercare

      Sticky header should include nav bar (On hold need UX)

      Open Prod Issues / Hypercare

      DNT-2654a11y audit - Form

      Execute test cases

      DNT-2569Add updated classes to Rich Text Editor in Sitecore CMS

      Add new rich text classes to electron/tailwind

      Sitecore - update rich text editor classes and elements

      Testing

      Secondary Nav for sub-sections

      JSS Application Component Conversion

      DNT-666Prod Issue: SOLR Search: Investigate our ability to jurisdictionally filter SOLR results

      Testing

      DNT-670Prod Issue: SOLR: Define how SOLR orders/prioritizes it's results

      Testing

      DNT-2209UX Review of JSS Test

      Sticky header should include nav bar

      DNT-2860Simple Calculator

      Develop test cases

      Sitecore Template and Rendering TDS Sync and Push

      DNT-2649a11y audit - Modal

      Labels provided for controls/elements are sufficiently descriptive

      Develop test cases

      Execute test cases

      DNT-2811a11y audit - MoreInfo

      Execute test cases

      DNT-2350JSS My Account Regression Testing

      In AUthenticated flow Confirmation page under Manage your new Account online module instead of ADD ACCOUNT text +ADD MORE text is displaying

      JSS - Dashboard Tiles (Explore New Ways To Save) giving error page

      JSS-Sign in-Remember Username/Email with check box is not displaying under the password field

      JSS-Unlock Additional Options- UAO not working from Products and Services page

      JSS-Authenticated Products and Service not being displayed

      JSS-Business Customer service page-Residential Customer Service page link is not underlined and Tree trimming displaying twice

      JSS-Forgot Username and Forgot Password Flows-Duke Logo on top of the page is missing

      JSS-Registration-After completing the Registration signing in for the first time, Password field displaying both the eye icon and Show button

      JSS- Products & Services - Online Savings Store page is not loading

      JSS- Customer Service - Authenticated page displays extra icons

      JSS- CARE Tiles & SSO Tile - Tiles displayed in SCQA and JSS test not matching for the same user

      JSS- Start, Stop & Move - Authenticated page Start, Stop & Move select moving Resources

      JSS-Sign in-Username/Email and Password fields not displaying error message when tabbed out blank

      Personalization Issues with Push down panel

      sccloudcachedev-wa.azurewebsites.net page when click on logo

      Cancel Button not working - find-it-duke/testing

      DNT-1271 Jurisdiction Selector Leverage Location Services

      CLONE - Jurisdiction Selector

      DNT-1060Stickiness for primary nav + Shrinking (animation)

      Testing

      DNT-548Jurisdiction Selector Intercept

      JURIS Intercept: FED

      JURIS Intercept: Sitecore

      JURIS Intercept: Testing

      DNT-1189Render Modal Video component on a page

      CLONE - Latest News: FED Conversion

      CLONE - Latest News: Sitecore Tempate

      CLONE - Latest News: Content Migration

      CLONE - Latest News: Functional Testing

      DNT-1366Component Conversion: Search Results Page

      Create route and call component on the page

      DNT-421choice-calculator (Convert to React Component)

      Setup Ohio Choice Calculator in CMS

      Accessibility updates to components

      Navigation Redesign

      Jurisdiction Selector Redesign

      Analytics Support for Modernization

      Forms Redesign

      Search Redesign

      JSS Code Base Maintenance

      JSS-Connect Intersection

      Integrate components and tooling from current app into the JSS Starter App

      JSS Governance and Standards

      Visual design reviews of components

      JSS Content Tasks

      DNT-1426Call to Action component button will not open in new window

      Testing

      JSS Spanish

      DNT-1725Merge Street Number and Street Address into a single field

      Testing

      Data Fetching

      State Management

      Monorepos / Micro Front-Ends

      Provide ability to toggle features on/off dynamically

      Post-migration Testing

      JSS Go-live activities

      DNT-2369Pre-migration Tasks

      Start/Stop/Move

      Originally a JSS 'Issue Type' = 'Bug'

      Track CMS Changes

      Fast followers after initial JSS release

      JSS Application Component Conversion

      Throttle scroll event listeners

      JSS Code Base Maintenance

      Upgrade to CRA 5

      JSS Code Base Maintenance

      Rename component folder

      JSS Code Base Maintenance

      Rec Sites & Water Access Phone field not rendering

      Enabler: Component Code Splitting

      JSS Code Base Maintenance

      Post-R8 Approved (Non-Optimization)

      Is Duke My Service Provider?

      JSS-Modernization: SCQA/SCTEST-Mobile- EV calculator Screen overlaps when scrolling up and down

      Test Data Attribution

      Is Duke My Service Provider?

      Set up Application Component and JSON data file for Upstream Distributor tables in Sitecore

      Multi-Step FormBuilder save error

    • DNT-799Component Conversion: ApplicationComponent

      Sitecore Template: ApplicationComponent

      DNT-803Component Conversion: Search

    • DNT-2640a11y audit - Jurisdiction Intercept

      Execute test cases

      DNT-2755a11y audit - Product Cards

    • DNT-2654a11y audit - Form

      Required and invalid form controls must convey/expose the fact that they are required and invalid

      DNT-2209UX Review of JSS Test

    • SEARCH - Generate new search term event for initial interaction with a pagination button on search results page

      Open Prod Issues / Hypercare

      DigitalFoundation Release Merge

    Only My Issuesarrow-up-right
    Recently Updatedarrow-up-right

    Summary of Priority Findings by WCAG 2.1 Success Criteriaarrow-up-right

    Explanation of Table Headersarrow-up-right

    User Journey: Common Componentsarrow-up-right

    1. Secondary Navarrow-up-right

    2. Footerarrow-up-right

    3. Footerarrow-up-right

    4. Primary Navarrow-up-right

    5. Footerarrow-up-right

    6. Common Issuesarrow-up-right

    User Journey: Component Libraryarrow-up-right

    7. Video Playerarrow-up-right

    8. News and Resourcesarrow-up-right

    9. Tablearrow-up-right

    10. Multi Videoarrow-up-right

    11. News and Resourcesarrow-up-right

    12. Push Down Panelarrow-up-right

    13. Flipboardarrow-up-right

    14. Tabarrow-up-right

    15. Hero Stock Chartarrow-up-right

    16. Accordion (FAQ)arrow-up-right

    17. Related Linksarrow-up-right

    18. More Infoarrow-up-right

    User Journey: Feedbackarrow-up-right

    19. Your Feedback Asidearrow-up-right

    20. Feedbackarrow-up-right

    User Journey: Formarrow-up-right

    21. Formarrow-up-right

    22. Formarrow-up-right

    23. Multi Step Formarrow-up-right

    24. Date Pickerarrow-up-right

    User Journey: Global Alertarrow-up-right

    25. Global Alertarrow-up-right

    User Journey: Homearrow-up-right

    26. Product Cardsarrow-up-right

    27. Jurisdiction Selectorarrow-up-right

    28. Searcharrow-up-right

    29. Searcharrow-up-right

    30. SignInarrow-up-right

    31. Jurisdiction Selectorarrow-up-right

    32. Jurisdiction Selectorarrow-up-right

    33. Jurisdiction Selectorarrow-up-right

    34. Nav Cardsarrow-up-right

    35. News Bannerarrow-up-right

    36. Top tasksarrow-up-right

    User Journey: New Componentsarrow-up-right

    37. Quick Linksarrow-up-right

    38. Modalarrow-up-right

    39. Call to actionarrow-up-right

    40. Bulleted Overviewarrow-up-right

    41. Twitterarrow-up-right

    42. Photo with Captionarrow-up-right

    43. Email Signuparrow-up-right

    44. Jurisdiction Interceptarrow-up-right

    User Journey: Pay My Billarrow-up-right

    45. Rectangular Cardarrow-up-right

    46. Centered Cardarrow-up-right

    User Journey: Searcharrow-up-right

    47. Search Results Pagearrow-up-right

    48. Search Results Pagearrow-up-right

    Appendix: Web Accessibility Testing Toolsarrow-up-right

    hashtag
    Engagement Overview

    TPGi conducted a Web Accessibility Review for Duke Energy Manual Audit on December 09, 2021 - January 31, 2022 at the request of Duke Energy to determine if it is possible for users who have visual disabilities and who use a screen reader to access the content and perform key functions on the website.

    hashtag
    Engagement Details

    Title: Duke Energy Manual Audit

    Engagement Type: Responsive Web Accessibility Review

    Standards: WCAG 2.1­

    Environment: Responsive Web

    Assistive Technologies: Firefox and NVDA, Safari and VoiceOver, Chrome and JAWS

    Testing Tools: ARC Toolkit, Colour Contrast Analyser, ARC Capture Extension

    Variant Schemas: Chrome - Desktop

    Technologies: HTML, CSS, ARIA, JavaScript

    hashtag
    User Journeys and Components Reviewed:

    A. Common Components

    1. Secondary Nav - https://scjsstest.duke-energy.com/homearrow-up-right

    2. Footer - https://scjsstest.duke-energy.com/homearrow-up-right

    3. Footer - https://scjsstest.duke-energy.com/homearrow-up-right

    4. Primary Nav - https://scjsstest.duke-energy.com/homearrow-up-right

    5. Common Issues - https://scjsstest.duke-energy.com/homearrow-up-right

    B. Component Library

    1. Video Player - https://scjsstest.duke-energy.com/home/products/power-managerarrow-up-right

    2. News and Resources - https://scjsstest.duke-energy.com/Our-Company/About-Us/Power-Plants/Asheville-Plantarrow-up-right

    3. Table - https://scjsstest.duke-energy.com/home/billing/billing-payment-optionsarrow-up-right

    4. Multi Video - https://scjsstest.duke-energy.com/our-company/careers/benefitsarrow-up-right

    5. Push Down Panel - https://scjsstest.duke-energy.com/home/billing/paperlessarrow-up-right

    6. Flipboard - https://scjsstest.duke-energy.com/our-company/about-usarrow-up-right

    7. Tab - https://scjsstest.duke-energy.com/home/products/renewable-energy/nc-shared-solararrow-up-right

    8. Hero Stock Chart - https://scjsstest.duke-energy.com/our-companyarrow-up-right

    9. Accordion (FAQ) - https://scjsstest.duke-energy.com/home/billing/paperlessarrow-up-right

    10. Related Links - https://scjsstest.duke-energy.com/our-company/environment/air-qualityarrow-up-right

    11. More Info - https://scjsstest.duke-energy.com/home-servicesarrow-up-right

    C. Feedback

    1. Your Feedback Aside - https://scjsstest.duke-energy.com/homearrow-up-right

    2. Feedback - https://scjsstest.duke-energy.com/homearrow-up-right

    D. Form

    1. Form - https://scjsstest.duke-energy.com/home-services/gas-line-repair/enrollarrow-up-right

    2. Multi Step Form - https://scjsstest.duke-energy.com/business/products/design-assistance/requestarrow-up-right

    3. Date Picker - https://scjsstest.duke-energy.com/business/products/design-assistance/request?first_name=test&last_name=test&company=sdsd&title=sds&Company_Role__c=Account+Executive&street=test&city=test&zip=08844&EDA_Project_Name__c=sdd&email=tes%40test.com&emailconf_predef=tes%40test.com&phone=9089639922&EDA_Anticipated_Permit_Date__c=&EDA_Schematic_Design_Date__c=&EDA_Design_Development_Date__c=&EDA_Construction_Documents_Date__c=&EDA_Construction_Completion_Date__c=&Building_Square_Footage__c=&Parking_Garage_Sq_Ft__c=&Data_Center_Sq_Ft__c=&Above_Grade_Stories__c=&Below_Grade_Stories__c=&Percent_Cooled__c=&Other_energy_design_alternatives__c=&Special_considerations_and_other_comment__c=arrow-up-right

    E. Global Alert

    1. Global Alert - https://sctest.duke-energy.com/home/healthcheckarrow-up-right

    F. Home

    1. Product Cards - https://scjsstest.duke-energy.com/homearrow-up-right

    2. Jurisdiction Selector - https://scjsstest.duke-energy.com/homearrow-up-right

    3. Search - https://scjsstest.duke-energy.com/homearrow-up-right

    4. SignIn - https://scjsstest.duke-energy.com/search-results?searchInput=undefinedarrow-up-right

    5. Nav Cards - https://scjsstest.duke-energy.com/homearrow-up-right

    6. News Banner - https://scjsstest.duke-energy.com/homearrow-up-right

    7. Top tasks - https://scjsstest.duke-energy.com/homearrow-up-right

    G. New Components

    1. Quick Links - https://scjsstest.duke-energy.com/home/products/outdoor-lighting#?type=fixtures&style=Sanibel%20LEDarrow-up-right

    2. Modal - https://scjsstest.duke-energy.com/home/products/renewable-energy/nc-solar-rebatesarrow-up-right

    3. Call to action - https://scjsstest.duke-energy.com/home/products/renewable-energy/nc-solar-rebatesarrow-up-right

    4. Bulleted Overview - https://scjsstest.duke-energy.com/our-company/about-usarrow-up-right

    5. Twitter - https://scjsstest.duke-energy.com/our-companyarrow-up-right

    6. Photo with Caption - https://scjsstest.duke-energy.com/our-company/about-us/leadership/lynn-j-goodarrow-up-right

    7. Email Signup - https://scjsstest.duke-energy.com/homearrow-up-right

    8. Jurisdiction Intercept - https://scjsstest.duke-energy.com/home/billingarrow-up-right

    H. Pay My Bill

    1. Rectangular Card - https://scjsstest.duke-energy.com/home/billingarrow-up-right

    2. Centered Card - https://scjsstest.duke-energy.com/home/billingarrow-up-right

    I. Search

    1. Search Results Page - https://scjsstest.duke-energy.com/search-results?searchInput=thearrow-up-right

    hashtag
    Findings

    Screen reader users will have a difficult time fully understanding the content and performing some functions.

    The following top key issues specific to the Duke Energy Manual Audit will make it difficult for screen reader users:

    Duke Energy's test sitearrow-up-right presents considerable challenges for people with visual and motor disabilities that use assistive technologies.

    There are significant accessibility issues in the site's template components, which appear on almost every site page. The site makes wide use of side panels to sign into the site, search the site, register one's email, and provide feedback.

    These side panels have the behavior of dialogs, but lack all required focus management techniques. When these panels appear, focus is not set to the panel's container or a focusable element within the panel. As a result, screen reader users are not aware that the panel has appeared. In most cases, the panel represents the last element in the page's markup. Assistive technology users must navigate through the entirety of page content to reach these panels.

    When the panels are visually hidden, they are positioned offscreen but remain accessible to screen readers and to users that navigate the page using the TAB key. As a result, when a keyboard user tabs onto the elements within these panels, it is not possible to visually determine what element is currently focused.

    The site makes wide use of decorative icons and images to augment the visible text. The visible text, by itself, is sufficient for screen reader users to understand the purpose and meaning of the content. These decorative elements do not convey meaningful information, yet they frequently have redundant, unnecessary text alternatives. These text alternatives increase the page noise, making site navigation more cumbersome for screen reader users.

    Despite the barriers present in the site's template components, a number of accessibility features are well-implemented. The site has a skip link to bypass the navigation region and jump to the main content region. The site's videos have closed captioning, ensuring that people that are deaf or hard of hearing can have the same experience as those able to fully hear the videos. Most site controls are keyboard accessible; a sighted, keyboard user should be able to access most of the site's core functionality. The site consistently has a visual focus indicator; however, given that the site uses the browsers' default focus outline, the visual focus indicator may at times, be hard for low vision and color-blind people, to perceive.

    hashtag
    User Journey and Component Findings

    This section of the report documents the accessibility findings that impact screen reader users, as well as recommended changes. The Web Accessibility Review included 48 components across 9 user journeys to determine what the user experience would be for a screen reader user.

    hashtag
    Summary of Priority Findings by WCAG 2.1 Success Criteria

    WCAG 2.1 Success Criteria

    Checkpoint

    Level

    Failure Count

    P1

    P2

    P3

    P4

    P5

    1.1.1 Non-text Content

    A

    78

    0

    3

    6

    NOTE: In addition, 13 Best Practice issues have been documented. While these are not failures of WCAG criteria, their implementation would improve the accessibility and usability of the user interface.

    hashtag
    Explanation of Table Headers

    1. # - A unique issue number for cross-referencing throughout the report.

    2. Issue Description - Description of where the page is not consistent with the specified accessibility standards.

    3. Modification - Suggestions for fixing the problem.

    4. STD (Standard) - The three-part numbers (e.g. 1.1.1) refer to the WCAG 2.1 success criteria (http://www.w3.org/TR/WCAG21/arrow-up-right).

    5. Count - Number of issues found.

    6. PRIO (Priority) - The priority column indicates the recommended order in which the issues are remediated.

    a. 1 are recommended to be remediated first. These issues will block access for a screen reader user or would make the content very difficult to understand.

    b. 2 is the next most important issue to be fixed. These issues are areas where a screen reader user will have a difficult time interacting with the site and understanding the content.

    c. 3 are a bit less 1 than the issues marked priority 1 and 2 and should be fixed after the issues identified as P1 and P2 are fixed.

    NOTE: The priority of an issue provides a guide to remediation priority only and is not necessarily the same as the A, AA, and AAA conformance levels of the WCAG success criteria related to the issue. Unless indicated otherwise, all issues listed in the report require remediation to fix the critical and high priority WCAG 2.1 conformance issues on these key screens and other similar pages that we did not review.

    hashtag
    User Journey: Common Components

    hashtag
    1. Secondary Nav

    hashtag
    Secondary Nav Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    1.1

    Landmark use is incomplete or incorrect

    When a page has multiple <nav> elements, each must be given a unique label. The <nav> element for the site's secondary navigation lacks a label to identify its purpose.

    The container for each expandable menu is marked up as a <nav>. This results in a <nav> containing a <nav>.

    Recommendation

    When using landmarks on a web page, ensure they are used correctly:

    1.3.1 Info and Relationships A

    5

    5

    1.2

    ARIA attribute applied to generic element

    hashtag
    2. Footer

    hashtag
    Footer Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    2.1

    Images of text are used

    Images of text can be problematic for users with low vision, color blindness, and cognitive impairments. When text is presented as an image, it is not possible for users to change the presentation of the text to suit their particular needs and preferences (such as changing the font/typeface, changing the foreground/background color to increase contrast, increasing line height or spacing).

    In addition, when text is presented using bitmap images (JPEG, PNG, GIF), it generally becomes blocky, blurry and harder to read for users that use browser zoom or magnification software.

    The "Building a Smarter Energy Future" text is an image of text and not HTML text.

    Recommendation

    Authors are encouraged to use "real" text in their web pages rather than making images of text (<img> elements, CSS background images, rendered dynamically on <canvas> elements, SVG path data, etc.), unless the particular way in which text is visually presented is essential and/or cannot be achieved using regular technologies like HTML/SVG/CSS (including, but not limited to, the use of text-shadow, web fonts, gradient backgrounds, rounded corners).

    1.4.5 Images of Text AA

    1

    4

    2.2

    Nested footer landmarks

    hashtag
    3. Footer

    hashtag
    Footer Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    No issues identified.

    hashtag
    4. Primary Nav

    hashtag
    Primary Nav Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    4.1

    Large scale text does not have a 3:1 color contrast ratio

    Large size text does not have sufficient contrast with its background. This means people with moderately low vision or color blindness will have problems reading the text.

    In the expandable side navigation menu, the buttons, when in hover state, lack sufficient color contrast between the background and the button text.

    Foreground: #E0F6FB Background: #00789E Contrast ratio: 1.3:1

    Recommendation

    Provide enough contrast between the foreground (text) color and background color so that people who are color-blind or with moderately low vision can read text and distinguish elements. For large text (at least 18pt / 24px, or bold and at least 14pt

    1.4.3 Contrast (Minimum) AA

    1

    4

    4.2

    Changes in language are not explicitly marked up

    hashtag
    5. Footer

    hashtag
    Footer Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    No issues identified.

    hashtag
    6. Common Issues

    hashtag
    Common Issues Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    No issues identified.

    hashtag
    User Journey: Component Library

    hashtag
    7. Video Player

    hashtag
    Video Player Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    7.1

    An audio description is not provided

    The video includes visual content that is important to the meaning and understanding of the video but it is not provided via audio description. Blind and low vision users will not be able to understand the content.

    Recommendation

    Provide an audio description of all meaningful visual aspects of the synchronized media presentation.

    An audio description is a specifically made audio track which, in addition to the regular audio elements of the media presentation, describes any meaningful visual aspects of the synchronized media presentation - this includes visible text, key visual elements such as graphics superimposed over a video (e.g. an arrow pointing to a UI element or a box around a group of UI elements), actions taking place in a video, or animations in an animated SVG diagram. This can be provided in various forms, ranging from an alternative audio track that a user can enable, a separate video file (in the case of a self-contained audio/video presentation), or a completely separate page with the synchronized media presentation which incorporates the audio description.

    For a good example of audio description, see , which includes audio description in the natural pauses of the regular narration track in the segments between 3:26 and 11:10.

    1.2.5 Audio Description (Prerecorded) AA

    1

    1

    7.2

    Video requires both horizontal and vertical scrolling when resized to a width of 320 CSS px / height of 256 CSS px

    hashtag
    8. News and Resources

    hashtag
    News and Resources Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    8.1

    Active SVG does not have a text alternative

    Links in the table open PDF documents. A PDF icon informs visual users that these links lead to PDFs. There is no text alternative for screen reader users. Additionally, the "PDF" text is very small and will be difficult to read for users with low vision.

    Recommendation

    Add "PDF" to the link text itself, and use aria-hidden="true' to hide the <svg> from assistive technologies. Also add focusable="false" to the <svg> element to prevent unnecessary tab stops.

    Simplified code example

    1.1.1 Non-text Content A

    5

    3

    8.2

    Sortable data tables are not defined and well formed

    hashtag
    9. Table

    hashtag
    Table Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    9.1

    Decorative images are not hidden from screen reader users

    Icons in the first column of the table are decorative image elements that are not hidden from screen reader users.. This can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    6

    4

    9.2

    Data tables are not defined as well-formed tables with correct row and column headers

    hashtag
    10. Multi Video

    hashtag
    Multi Video Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    10.1

    Button does not indicate whether it is pressed

    The video tile buttons do not indicate when they are selected. When a video tile button is selected, the video opens in the adjacent video player, but it does not trigger an announcement to assistive technology.

    Recommendation

    Ensure that when a video is selected, it triggers an announcement to assistive technology which conveys that the page content has updated.

    Option 1

    The best and most appropriate solution is to apply an aria-pressed attribute to each video tile button. Toggle the button to reflect whether that video is selected.

    Simplified, recommended markup using aria-pressed

    4.1.2 Name, Role, Value A

    5

    3

    10.2

    Text alternative includes superfluous information

    hashtag
    11. News and Resources

    hashtag
    News and Resources Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    11.1

    Filter control does not have a persistent visible label

    The "show" <select> element has a label that is visually hidden until the user selects a value. The purpose of the control may not be clear to users.

    Recommendation

    Ensure that the "Show" label is always visible. Form controls that require user input must have an appropriate label that describes the expected input to all users. For form controls that have additional restrictions or input requirements (such as requiring a specific format or a specific piece of information that is not obvious from the label alone), some form of instruction must be present to inform the user of these specific restrictions or requirements. This information must be permanently available and cannot be removed once a value is entered in the control.

    Resources

    3.3.2 Labels or Instructions A

    1

    3

    hashtag
    12. Push Down Panel

    hashtag
    Push Down Panel Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    12.1

    Elements that are not conveying headings are marked up as headings

    When the content is expanded, the first text for each expanded section is marked up as a heading. This content is a repeat of the heading above it and is marked up as the same heading level (<h3>). This can be confusing for users, especially assistive technology users who navigate the page by heading.

    Recommendation

    Do not use heading markup for text that does not serve as a descriptive label for the section of content that follows it.

    1.3.1 Info and Relationships A

    3

    4

    12.2

    Underlined buttons appear visually as links

    hashtag
    13. Flipboard

    hashtag
    Flipboard Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    13.1

    Decorative images are not hidden from screen reader users

    The image within each link is decorative. Each image has inappropriate alt="image" attribute. This extra "page noise" can cause confusion for screen reader users. Screen reader users will wonder whether the image is actually informative and its information is not adequately announced to them.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    4

    4

    13.2

    Inappropriate title attribute

    hashtag
    14. Tab

    hashtag
    Tab Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    14.1

    Decorative images are not hidden from screen reader users

    Each element with role="tab" has has an aria-label attribute which provides the control's accessible name. Each tab's descendant image has an alt attribute which has a redundant text alternative e.g. alt="icon". Given the aria-label attribute on the image's functional container, the element with role="tab", the descendant image can be considered decorative; its alt attribute is unnecessary.

    Recommendation

    Use an empty string as text alternative alt=""

    1.1.1 Non-text Content A

    6

    4

    14.2

    Tab panel is difficult to operate using a mouse

    hashtag
    15. Hero Stock Chart

    hashtag
    Hero Stock Chart Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    15.1

    Decorative images are not hidden from screen reader users

    The image of a stock chart is a decorative image. It has redundant alt="chart" attribute.

    The image, representing the background for the section has inappropriate alt="patterned background for duke Energy grant notification copy" attribute.

    These unnecessary text alternatives extra "page noise" can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    1.1.1 Non-text Content A

    2

    4

    hashtag
    16. Accordion (FAQ)

    hashtag
    Accordion (FAQ) Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    16.1

    Decorative images are not hidden from screen reader users

    There is an image of a question mark above the "Frequently Asked Questions" heading. This image is announced to screen reader users as "Image". This can cause confusion for screen reader users. Users will wonder if there is an informative image that is not adequately announced to them.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    1

    4

    16.2

    Link does not have any text

    hashtag
    17. Related Links

    hashtag
    Related Links Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    No issues identified.

    hashtag
    18. More Info

    hashtag
    More Info Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    18.1

    A decorative image is not hidden from screen reader users

    The decorative image, of an info "i" icon, is not hidden from assistive technologies. It has a redundant alt="image" attribute. This can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    1

    4

    18.2

    Dynamically-added content is not added to the focus order directly after the element that exposed the content

    hashtag
    User Journey: Feedback

    hashtag
    19. Your Feedback Aside

    hashtag
    Your Feedback Aside Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    19.1

    Active `<img>` does not have a text alternative

    An <img> is the sole element within the button to close the feedback aside. The image is missing a text alternative. As a result, the control, has no text representation that could provide an accessible name.

    Screen reader users are not able to understand the control's purpose; screen readers mostly ignore the button to close the feedback aside because it is a generic <div> but has no text.

    Recommendation

    For <img> elements that need to contribute content to the accessible name of a link or button, apply an alt attribute with a value briefly describing the link target/button action and including any text displayed in the image.

    1.1.1 Non-text Content A

    1

    2

    19.2

    There is a loss of content or functionality when text is resized up to 200%

    hashtag
    20. Feedback

    hashtag
    Feedback Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    20.1

    CSS-generated background image lacks a text alternative

    The control to open the Feedback widget is a <div> with a CSS-generated background image. The image does not have a text alternative. The control is a generic <div> element and does not expose a button role. Screen readers mostly ignore the control. The control has a tabindex="0" attribute. When the control receives focus, screen readers either announce "clickable" or make no announcement at all.

    The lack of accessible role and state information is discussed in a separate assertion.

    Recommendation

    Ensure the control has an accessible name. The most straightforward fix is to:

    1.1.1 Non-text Content A

    1

    2

    20.2

    Images of text are used

    hashtag
    User Journey: Form

    hashtag
    21. Form

    hashtag
    Form Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    21.1

    Informative SVG does not have text alternative

    The check mark that indicates that the form has been successfully submitted is an <svg> image without a text alternative. Blind and low vision users will not have the same immediate understanding that the form has submitted successfully.

    Recommendation

    Use the SVG <title> element to provide a text alternative that's appropriate for the context. To ensure support in assistive technologies, provide role="img" and aria-labelledby attributes in the <svg> element. To ensure that <svg> elements do not receive keyboard focus, add focusable="false"

    1.1.1 Non-text Content A

    1

    4

    21.2

    Content is presented as list visually but not coded as list

    hashtag
    22. Form

    hashtag
    Form Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    22.1

    Changing text spacing results in a loss of content

    When text spacing styles are changed, the label for "If contact by phone is your preference, what time of day is best" is obscured and no longer intelligible. Users with low vision or cognitive disabilities, may need to restyle text to make it easier to read (by increasing style properties such as line height, letter spacing and word spacing)

    Recommendation

    No loss of content or functionality must occur when setting the following text style properties and by changing no other property:

    • line height (line spacing) to at least 1.5 times the font size;

    1.4.12 Text Spacing AA

    1

    3

    22.2

    List markup is missing

    hashtag
    23. Multi Step Form

    hashtag
    Multi Step Form Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    23.1

    SVG images do not have a text alternative

    The check mark that indicates a completed step in the form is an <svg> image without a text alternative. Blind and low vision users will not know that this is a completed step.

    Recommendation

    Use the SVG <title> element to provide a text alternative that's appropriate for the context. To ensure support in assistive technologies, provide role="img" and aria-labelledby attributes in the <svg> element.

    SVG with <title>

    1.1.1 Non-text Content A

    1

    4

    23.2

    Content requires both horizontal and vertical scrolling when resized to a width of 320 CSS px / height of 256 CSS px

    hashtag
    24. Date Picker

    hashtag
    Date Picker Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    24.1

    Content is obscured when resized to a width of 320 CSS px / height of 256 CSS px

    In smaller viewports or when zoomed in:

    • The date picker button is obscured by the "Feedback" button.

    • If the date picker is open, the right side, including next month and year buttons, Friday and Saturday dates, and half of the close button are obscured by the "Feedback" button.

    1.4.10 Reflow AA

    2

    3

    24.2

    The date picker does not follow the established design pattern

    hashtag
    User Journey: Global Alert

    hashtag
    25. Global Alert

    hashtag
    Global Alert Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    25.1

    The parsed DOM of the page has errors that may prevent assistive technology from correctly interpreting content

    The DOM contains one parsing error:

    Elements are not nested according to their specifications. There are several <div> elements within the <button> element.

    Ensure all content is structured correctly using the most semantically appropriate elements. Ensure markup has complete start and end tags, is nested correctly according to the specification, has well-formed attribute values, and that id="..." attribute values are unique for the page.

    1. Add the

    4.1.1 Parsing A

    1

    5

    25.2

    Content overlaps and meaning is lost when resized to a width of 320 CSS px / height of 256 CSS px

    hashtag
    User Journey: Home

    hashtag
    26. Product Cards

    hashtag
    Product Cards Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    26.1

    Link text does not indicate the purpose of the link

    The "Download the Duke Energy Mobile App" link text does not describe the link destination, which is the Products and Services page. This is likely to be confusing for users, who will need to search the destination page for another link to download the app. Also, there are other links on this page that load the Products and Services page, which is likely to add to the confusion.

    Recommendation

    The link text must describe the destination in a way that enables users to understand what will happen when it is activated. In this instance, for example, the text "Download the Duke Energy Mobile App" could well lead users to assume that activating the link would take them to an app store, or at least to a page whose main content described how to download the mobile app.

    Either the link should be changed to provide a more accurate description of the destination page, or activating the link should load a page describing how to download the app.

    Resources

    2.4.4 Link Purpose (In Context) A

    1

    2

    26.2

    Text that is not conveying headings is exposed as headings

    hashtag
    27. Jurisdiction Selector

    hashtag
    Jurisdiction Selector Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    27.1

    The Zipcode field does not expose its invalid state

    The ZipCode field is visually indicated as invalid when incorrect data such as not enough digits is entered, but this state is not communicated programmatically. Assistive technology users may have difficulty determining when this field is invalid.

    Recommendation

    Set aria-invalid="true" on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the aria-invalid attribute or set it to false.

    Setting aria-invalid

    4.1.2 Name, Role, Value A

    1

    2

    27.2

    Input fields collecting user information do not programmatically expose their purpose

    hashtag
    28. Search

    hashtag
    Search Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    28.1

    Headings do not appropriately describe the content/section that they relate to

    When entering a search, the list of results that is dynamically displayed beneath it has a heading that says "People Also Ask." This text does not flag up that what comes after is a list of search results.

    Recommendation

    Ensure that heading text is sufficiently clear and descriptive. It must accurately describe the content that follows it. In this instance, "Search Results" would be a descriptive heading.

    2.4.6 Headings and Labels AA

    1

    3

    28.2

    The search panel does not follow the established modal dialog design pattern

    hashtag
    29. Search

    hashtag
    Search Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    29.1

    Inappropriate navigation landmark

    A <nav> serves as the container for the search side panel. This is inappropriate. A navigation region should only contain navigation controls.

    Recommendation

    Remove the <nav> as a container. The container for the search results panel must have role="dialog". This is discussed at length in other assertions for this component.

    1.3.1 Info and Relationships A

    1

    3

    hashtag
    30. SignIn

    hashtag
    SignIn Technical Details

    | # | Issue Description | Modification | STD | Count | PRIO | | -------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------- | -------- | --- | | 30.1 | Decorative SVG is not hidden from screen reader users |

    The SVG preceding each form control is decorative but is not hidden from assistive technology.

    Recommendation

    As with bitmap images, any decorative SVG images should be hidden from assistive technologies, so that they do not add extra verbosity when a user is navigating around a page.

    When an SVG image is used for decoration only, the aria-hiddenarrow-up-right attribute must be used to hide the image completely from assistive technologies. Using aria-hidden means the image will remain visible on the page.

    It is further suggested to apply focusable="false" to the SVG.

    SVG with the aria-hidden attribute

    ... </svg>

    | 1.1.1 Non-text Content A | 2 | 4 | | 30.2 | There is a loss of content or functionality when text is resized up to 200% |

    When the browser is set to 1024 x 768 pixels, and zoomed to 200%, the content below the form fields to enter username and password becomes clipped off screen. In the sign up panel, it is not possible to vertically scroll and bring the content into the viewport.

    Recommendation

    Make sure that all text responds to changes in text size settings, and that elements do not overlap or get clipped when resized.

    The easiest solution is to add a vertical scrollbar within the signup panel. Ensure that a keyboard user can view and interact with the controls below the form fields for username and password, without using a mouse.

    General recommendations on building responsive web applications:

    • Use em-based container and font sizes. Setting the size of containers using em units means that the container will adapt in size to reflect any changes in text size within those containers. In terms of text resizing, em units are more robust than px units, particularly when using Internet Explorer, which won't resize text that has been set using absolute measurements.

    • Use fluid widths for layouts. For example, if you set the width of the main container for your site to 100% (rather than, say, 960px), this will mean that the container will always span the length of the browser window, regardless of the size of this window. In other words, you limit horizontal scrolling.

    • Use responsive layouts. Using media queries, you can re-order the layout of content based on the size of the viewport. Consequently, if a user zooms into the page, or uses text resizing, the page will automatically adapt to the new viewport size.

    Resources

    • Responsive web design basicsarrow-up-right

    • Understanding Resize textarrow-up-right

    | 1.4.4 Resize text AA | 1 | 2 | | 30.3 | Hidden content that affects understanding is announced by screen readers |

    The component has the behavior and functionality of dialog. When the component appears, a dimmed overlay covers the main page content. At smaller viewports, the widget to sign into the site consumes the full screen. The content outside the sign in panel is not hidden from assistive technology. Assistive technology users are able to reach this content and move back into the main document behind the component, using their virtual cursor (or alternative navigation methods, such as navigation by heading, landmark, etc.)

    Recommendation

    When a modal dialog is displayed, any content that is not part of the modal dialog content must not be included in the focus order (since the dialog should "contain" focus). However, content should also be hidden from assistive technologies, to avoid that AT users navigate back into the underlying main document using alternative methods (such as the virtual cursor, navigation by headings, etc).

    Example code: setting aria-hidden="true" on the container element for any non dialog content when the dialog is open

    <body> <div`` ``aria-hidden="true"> <main> ... </main> </div>\

    <div role="dialog" class="visible"`` ``aria-labelledby="dialog-title">

    </div> </body>

    For a solid implementation of an accessible custom dialog, see A11y Dialogarrow-up-right.

    Resources

    HTML5 Accessibility Chops: hidden and aria-hiddenarrow-up-right

    | 1.3.2 Meaningful Sequence A | 1 | 2 | | 30.4 | Lack of dialog focus management techniques |

    The widget to sign into one's account has the behavior of a dialog. However, the component has a total lack of the focus management techniques required for dialogs.

    • When the dialog appears, focus is not set to the component. Screen reader users are unaware that the dialog is present on the page; it is the last element on the DOM. A screen reader user would have to read through all page content to reach the dialog.

    • Focus is not constrained to the dialog. A keyboard user is able to interact with content outside the component. As a result, it is not possible to determine what element is currently focused.

    Recommendation

    When the sign in dialog is launched, focus must be moved to the dialog. There are multiple acceptable focus targets.

    The safest option is to set focus to the dialog's container. The component currently lacks dialog markup.

    • Apply role="dialog" to the container.

    • To accommodate legacy browsers, apply tabindex="-1" to the element with role="dialog".

    • Also apply aria-labelledby attribute to the dialog container and reference the top-level heading "Select your location". This requires adding an id to the heading.

    The dialog must contain focus. Using the TAB key should cycle through the focusable elements within the dialog. To achieve this, use JavaScript to query the dialog and return an array of all the focusable elements within the dialog. Elements that can receive focus are anchors, form input elements, buttons, and any element with a tabindex of 0. When the user has focus set to the last focusable element and presses TAB, set focus to the first focusable element in the dialog. Similarly, when focus is set to the first focusable element in the dialog and the user presses SHIFT+TAB, set focus to the last focusable element in the dialog.

    This assertion covers the component's lack of focus management; the lack or ARIA and role attributes is reported in a separate 4.1.2arrow-up-right assertion.

    Resources

    Modal Dialog Example | WAI-ARIA Authoring Practices 1.2arrow-up-right

    | 2.4.3 Focus Order A | 1 | 2 | | 30.5 | Input fields collecting information about the user do not programmatically expose their purpose |

    There are input fields asking for personal data which are not enabled to provide autocomplete information. This will make it more difficult for some users to enter their information.

    The form controls for email address and password lack the appropriate autocomplete attributes

    Recommendation

    Input fields collecting information about the user must programmatically expose their purpose. For web content, ensure that known/common inputs (such as name, home/business address, email, phone number, and any other common Input Purposes for User Interface Componentsarrow-up-right relating to personal user information) are identified explicitly in markup using relevant autocomplete values from HTML Living Standardarrow-up-right. Doing so enables user agents and tools (such as browsers, password managers, and assistive technologies) to provide useful functionality such as auto-filling fields with stored personal information (meaning users don't have to remember / enter this information manually).

    | 1.3.5 Identify Input Purpose AA | 2 | 2 | | 30.6 | The sign in panel does not follow the established modal dialog design pattern |

    The sign in panel has the behavior of dialog. However, it does not present to assistive technologies as a dialog and has the following issues:

    • Lack of a role="dialog" attribute.

    • Lack of an accessible name.

    • Lack of an accessible description.

    • Neither the dialog, nor a descendant of the dialog, is focused when the dialog is displayed.

    • Keyboard focus is not properly constrained to the modal dialog while it is active.

    • Focus management issues are discussed in a separate WCAG Level A 2.4.3 Focus Order assertion.

    • Pressing ESCAPE on the keyboard does not dismiss the dialog.

    • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing. This is discussed in a separate WCAG Level A 1.3.2 Meaningful Sequence assertion

    Recommendation

    • For dialogs with a visible title, use an aria-labelledby reference to the element containing the title text (usually the <h2>) to provide an accessible name. In this case, "Sign in" serves as a title and could be used. Be sure to identify this text as a heading.

    • For dialogs like this that do not have a visible title, use aria-label to provide an accessible name for the dialog.

    • For dialogs with a simple message in the dialog body, use aria-describedby to reference the element containing the text.

    • Add the role dialog to the containing element.

    • Ensure that the dialog or a descendant element of the dialog is focused when it is displayed, and that the trigger that caused the dialog to open (the Search button) receives focus when the dialog is dismissed.

    • Ensure that pressing ESCAPE on the keyboard dismisses the dialog.

    • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

    • ARIA Authoring Practices Modal Dialog Examplearrow-up-right

    • Accessible Modal Dialogarrow-up-right

    • a11y-dialog widgetarrow-up-right

    | 4.1.2 Name, Role, Value A | 1 | 2 | | 30.7 | Inappropriate navigation landmark |

    A <nav> serves as the container for the sign in panel. This is inappropriate. A navigation region should only contain navigation controls.

    Recommendation

    Remove the <nav> as a container. The container for the search results panel must have role="dialog". This is discussed at length in other assertions for this component.

    | 1.3.1 Info and Relationships A | 1 | 3 |

    hashtag
    31. Jurisdiction Selector

    hashtag
    Jurisdiction Selector Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    31.1

    The panel does not follow the established modal dialog design pattern

    This component looks and behaves like a modal dialog in that it dims the underlying content. However, it does not present to assistive technologies as a dialog and has the following issues:

    • Lack of the expected dialog role.

    • Lack of an accessible name.

    4.1.2 Name, Role, Value A

    19

    2

    31.2

    Focus is not managed within the dialog

    hashtag
    32. Jurisdiction Selector

    hashtag
    Jurisdiction Selector Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    No issues identified.

    hashtag
    33. Jurisdiction Selector

    hashtag
    Jurisdiction Selector Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    33.1

    The panel does not follow the established modal dialog design pattern

    The Jurisdiction side panel looks and behaves like a modal dialog in that it dims the underlying content. However, it does not present to assistive technologies as a dialog and has the following issues:

    • Lack of an accessible name.

    • Lack of an accessible description.

    4.1.2 Name, Role, Value A

    1

    2

    33.2

    Focus is not managed within the dialog

    hashtag
    34. Nav Cards

    hashtag
    Nav Cards Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    34.1

    Decorative images are not hidden from screen reader users

    The five decorative images (one beside the "Our Company" heading and one on each nav card) do not convey information and are not hidden from screen reader users. Hearing the screen reader announce alt text for images that are not essential to the understanding of the page can be confusing and cause extra "page noise" for blind users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    5

    4

    34.2

    Labels provided for links are not sufficiently descriptive (subjective)

    hashtag
    35. News Banner

    hashtag
    News Banner Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    35.1

    Link text does not indicate the purpose of the link

    The link text of the "View Resources" link does not provide adequate context on its own, nor is it programmatically associated with other content which could provide such context.

    Recommendation

    Where possible, provide a more descriptive visible link phrase for all users, such as "More info on special assistance."

    In the absence of any additional context, link text must clearly indicate the target of the link. In cases of ambiguous link text such as "View Resources", provide additional information on the purpose of the link - for instance, by using visually-hidden text.

    Recommended markup (simplified) for "View Resources" link, with visually-hidden text to provide further context

    If you are unable to provide visually-hidden text, you can use the aria-label

    2.4.4 Link Purpose (In Context) A

    1

    3

    35.2

    Decorative images are not hidden from screen reader users

    hashtag
    36. Top tasks

    hashtag
    Top tasks Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    36.1

    A decorative image is not hidden from screen reader users

    The decorative image of a drone operator is not hidden from assistive technologies as the alt attribute contains a short description. This can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    1

    4

    36.2

    Focusable components lack a visible focus indication when focused

    hashtag
    User Journey: New Components

    hashtag
    37. Quick Links

    hashtag
    Quick Links Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    37.1

    Decorative images are not hidden from screen reader users

    The decorative images within each link are not hidden from screen reader users. Instead, each has alternative text that is added to the visible text for link text such as "Image Design a light" and "icon book Outdoor lighting terminology." This can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies. One of the links, "Request a light repair," also includes a title which repeats the link text. Removing it will make the link announcement less verbose.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    5

    0

    37.2

    Text overlaps when resized to a width of 320 CSS px / height of 256 CSS px

    hashtag
    38. Modal

    hashtag
    Modal Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    38.1

    Third party content is not accessible

    Web authors are responsible for ensuring that all third party content is accessible, including:

    • Content that is part of a service supplied by an external provider, such as ecommerce processes, online booking systems, product shipping tracking, and standalone user login systems.

    • Content in the form of media supplied by an external provider, such as embedded videos from providers like YouTube and Vimeo, commercial news feeds, social media feeds, online entertainment feeds, games, tickers, and advertisements.

    1

    5

    38.2

    Links or buttons perform incorrect actions

    hashtag
    39. Call to action

    hashtag
    Call to action Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    39.1

    Link text should include PDF file type

    Links that open a PDF do not convey that information in the link text.

    Recommendation

    Ensure that if the target of the link is not another web page, then some information about the file type and size is provided.

    <a href="...">Loan sale announcement (PDF, 11KB)</a>

    The above technique may be enhanced by supplying an image to indicate the file type and giving that image alt text.

    Resources

    4

    4

    39.2

    Link opens in a new window or tab

    hashtag
    40. Bulleted Overview

    hashtag
    Bulleted Overview Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    40.1

    Decorative images are not hidden from screen reader users

    Each icon, adjacent to descriptive text, is a decorative image that is not hidden from screen reader users. This can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    7

    4

    40.2

    Text overlaps when resized to a width of 320 CSS px / height of 256 CSS px

    hashtag
    41. Twitter

    hashtag
    Twitter Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    41.1

    SVG images do not have a text alternative

    The Twitter logo <svg> image(s) is not described adequately. Blind and low vision users will not be able to understand the image(s).

    Recommendation

    When an <svg> image requires a more detailed plain text description, you may use the SVG element in conjunction with the attribute.

    SVG with <desc> element and the aria-describedby attribute

    1.1.1 Non-text Content A

    1

    2

    41.2

    Decorative images are not hidden from screen reader users

    hashtag
    42. Photo with Caption

    hashtag
    Photo with Caption Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    42.1

    Link opens in a new window or tab

    The Download Photo link opens in a new tab or window, but does not inform the user of this action.

    Recommendation

    When a link opens in a new tab or window, indicate this as part of the link text. The most common approach is to add an icon to the link with an 'opens in new window' text alternative.

    Resources

    1

    4

    hashtag
    43. Email Signup

    hashtag
    Email Signup Technical Details

    | # | Issue Description | Modification | STD | Count | PRIO | | -------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | ------------------- | -------- | --- | | 43.1 | Headings are not marked up as headings |

    The text "@ Sign Up Now" functions as a heading yet is marked up as a generic <span> within the button to close the email sign up widget.

    Simplified markup for visual heading

    <button aria-label="close"> <span>@ Sign Up Now</span> </button>

    Recommendation

    Ensure headings are defined correctly:

    • Use HTML heading markup (<h1> to <h6>) to semantically identify headings (after adding the alternative text, per another assertion).

    • Only if <h1> to <h6> elements cannot be used, role="heading" and aria-level attributes can be used. The value of the aria-level attribute must be a number ranging from 1 to 6, indicating the heading's level.

    • Only use a heading as a descriptive label for the section of content that follows it. Do not use headings solely for visual styling

    For the text in the email sign up widget, mark up the text as an <h2> and move it outside the <button>.

    Simplified, recommended markup for heading

    <h2>@ Sign Up Now</h2> <button`` ``aria-label="close @ Sign Up Now"> <span>@ Sign Up Now</span> </button>

    Resources

    • W3C Headings tutorialarrow-up-right

    • HTML heading elementsarrow-up-right

    | 1.3.1 Info and Relationships A | 1 | 3 | | 43.2 | Hidden content that affects understanding is announced by screen readers |

    The component has the behavior and functionality of dialog. At smaller viewports, the widget to sign up for email consumes the full screen. The content outside the component is not hidden from assistive technology. Assistive technology users are able to reach this content, and move back into the main document behind the modal, using their virtual cursor (or alternative navigation methods, such as navigation by heading, landmark, etc.)

    Recommendation

    When a modal dialog is displayed, any content that is not part of the modal dialog content must not be included in the focus order (since the dialog should "contain" focus). However, content should also be hidden from assistive technologies, to avoid that AT users navigate back into the underlying main document using alternative methods (such as the virtual cursor, navigation by headings, etc.).

    Example code: setting aria-hidden="true" on the container element for any non dialog content when the dialog is open

    <body> <div`` ``aria-hidden="true"> <main> ... </main> </div>

    <div role="dialog" class="visible"`` ``aria-labelledby="dialog-title"> <h2`` ``id="dialog-title">@ Sign Up Now</h2> </div> </body>

    For a solid implementation of an accessible custom dialog, see A11y Dialogarrow-up-right.

    Resources

    HTML5 Accessibility Chops: hidden and aria-hiddenarrow-up-right

    | 1.3.2 Meaningful Sequence A | 1 | 2 | | 43.3 | Lack of dialog focus management techniques |

    The widget to sign up one's email address has the behavior of a dialog. However, the component has a total lack of the focus management techniques required for dialogs.

    • When the dialog appears, focus is not set to the component. Screen reader users are unaware that the dialog is present on the page; it is the last element on the DOM. A screen reader user would have to read through all page content to reach the dialog.

    • Focus is not constrained to the dialog. A keyboard user is able to interact with content outside the component. As a result, it is not possible to determine what element is currently focused.

    Recommendation

    When the sign up dialog is launched, focus must be moved to the dialog. There are multiple acceptable focus targets.

    The safest option is to set focus to the dialog's container. The component currently lacks dialog markup.

    • Apply role="dialog" to the container.

    • To accommodate legacy browsers, apply tabindex="-1" to the element with role="dialog".

    • Also apply aria-labelledby attribute to the dialog container and reference the top-level heading "Select your location". This requires adding an id to the heading.

    The dialog must contain focus. Using the TAB key should cycle through the focusable elements within the dialog. To achieve this, use JavaScript to query the dialog and return an array of all the focusable elements within the dialog. Elements that can receive focus are anchors (with href), form input elements, buttons, and any element with a tabindex of 0. When the user has focus set to the last focusable element and presses TAB, set focus to the first focusable element in the dialog. Similarly, when focus is set to the first focusable element in the dialog and the user presses Shift+TAB, set focus to the last focusable element in the dialog.

    This assertion covers the component's lack of focus management; the lack of ARIA and role attributes is reported in a separate 4.1.2arrow-up-right assertion.

    Resources

    Modal Dialog Example | WAI-ARIA Authoring Practices 1.2arrow-up-right

    | 2.4.3 Focus Order A | 1 | 2 | | 43.4 | Controls do not have an accessible name |

    The <input> for an email address and the <select> for U.S. state lack accessible names. Each form control has an associated <label> but the <label> is hidden using visibility: hidden. As a result, screen readers cannot derive an accessible name from the <label>. For example, when focus is set to the <select>, NVDA announces the currently selected option and the form's <label>, "State", which would be an appropriate accessible name, is not announced.

    Recommendation

    The accessible name is the text that identifies and describes the control to assistive technology users. It is programmatically exposed by the browser to assistive technology products (such as screen readers or speech recognition tools).

    There are many methods to provide an accessible namearrow-up-right for the form controls using native HTML elements/attributes or ARIA attributes.

    In the sign up dialog, the form controls for email address and U.S. state rely on the placeholder text or option to provide the visual label. When one interacts with the form control, there is a loss of a visual label. This is a separate 3.3.2arrow-up-right assertion.

    The best approach is to remove visibility: hidden from each form control's <label>. Instead, make each <label> into an always visible label.

    <label for="form-email">Email Address</label> <input type="email" value id="form-email">

    Resources

    • What is an accessible name?arrow-up-right

    • The label elementarrow-up-right

    | 4.1.2 Name, Role, Value A | 2 | 1 | | 43.5 | Input fields collecting information about the user do not programmatically expose their purpose |

    There are input fields asking for personal data which are not enabled to provide autocomplete information. This will make it more difficult for some users to enter their information.

    The form controls for email address and state of residence lack the appropriate autocomplete attributes

    Recommendation

    Input fields collecting information about the user must programmatically expose their purpose. For web content, ensure that known/common inputs (such as name, home/business address, email, phone number, and any other common Input Purposes for User Interface Componentsarrow-up-right relating to personal user information) are identified explicitly in markup using relevant autocomplete values from HTML Living Standardarrow-up-right. Doing so enables user agents and tools (such as browsers, password managers, and assistive technologies) to provide useful functionality such as auto-filling fields with stored personal information (meaning users don't have to remember / enter this information manually).

    Specifying purpose using the autocomplete attribute

    <label for="form-email">Email Address</label>

    ... <label for="form-state">State</label>

    </select>

    | 1.3.5 Identify Input Purpose AA | 2 | 2 | | 43.6 | Error messages are not associated with form controls |

    Form controls to enter an email address and select a U.S. state that are in the error state have error text messages that are not programmatically associated. Blind or low vision users may not be aware of the messages.

    Recommendation

    Ensure error messages are programmatically associated with their form controls, so that users know the controls are in error without having to explore surrounding text. To create the programmatic relationship between the control and the error message, apply aria-describedby attribute to each form control and reference the id for the form control's error message. This requires adding an id to each error message.

    Simplified, recommended markup for form control with associated error message

    <label for="form-email" class="form-label">Email Address</label>

    | 3.3.1 Error Identification A | 2 | 2 | | 43.7 | Form controls rely on placeholder text for visual label |

    The form controls to enter an email address and select a U.S. state rely on placeholder text for their visual labels.

    • Placeholder text disappears when a user starts typing. This means that there is no visible label when users are entering text into the input, which could affect users with short-term memory issues.

    Note: Each form control has a programmatically associated <label>. They have text, "Email Address" and "State", that could provide an appropriate accessible name for the form control. However, each <label> is hidden using display: none, and so, some screen readers do not announce the <label>. For example, NVDA announces the placeholder text, "Email address" for the <input> and the currently selected option for the <select>. This lack of an accessible name is discussed in a separate 4.1.2 assertion.

    Recommendation

    Provide an always visible and programmatically associated label for each form control. The most straightforward solution is to make each <label> always visible.

    Resources

    HTML5 Accessibility Chops: the placeholder attributearrow-up-right

    | 3.3.2 Labels or Instructions A | 2 | 3 | | 43.8 | The search panel does not follow the established modal dialog design pattern |

    The email signup widget has the behavior of dialog. However, it does not present to assistive technologies as a dialog and has the following issues:

    • Lack of a role="dialog" attribute.

    • Lack of an accessible name.

    • Lack of an accessible description.

    • Neither the dialog, nor a descendant of the dialog, is focused when the dialog is displayed.

    • Keyboard focus is not properly constrained to the modal dialog while it is active.

    • Focus management issues are discussed in a separate WCAG Level A 2.4.3 Focus Order assertion.

    • Pressing ESCAPE on the keyboard does not dismiss the dialog.

    • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing. This is discussed in a separate WCAG Level A 1.3.2 Meaningful Sequence assertion

    Recommendation

    • For dialogs with a visible title, use an aria-labelledby reference to the element containing the title text (usually the <h2>) to provide an accessible name. In this case, "@ Sign Up Now" serves as a title and could be used. Be sure to identify this text as a heading.

    • For dialogs like this that do not have a visible title, use aria-label to provide an accessible name for the dialog.

    • For dialogs with a simple message in the dialog body, use aria-describedby to reference the element containing the text.

    • Add the role dialog OR alertdialog to the containing element.

    • Ensure that the dialog or a descendant element of the dialog is focused when it is displayed, and that the trigger that caused the dialog to open (the Search button) receives focus when the dialog is dismissed.

    • Ensure that pressing ESCAPE on the keyboard dismisses the dialog.

    • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

    • ARIA Authoring Practices Modal Dialog Examplearrow-up-right

    • Accessible Modal Dialogarrow-up-right

    • a11y-dialog widgetarrow-up-right

    | 4.1.2 Name, Role, Value A | 1 | 2 | | 43.9 | Use of aria-hidden="false" could be problematic |

    The email widget's container has an aria-hidden attribute which toggles to reflect whether the widget is displayed or hidden. When the widget is displayed, the container has an aria-hidden="false" attribute which could be problematic for screen reader users.

    In some CSS frameworks, browsers will treat the aria-hidden attribute as a Boolean attribute similar to the checked or required attribute, where the presence of the attribute alone, regardless of its value, is evaluated as true. In other words, browsers will interpret aria-hidden="<any phrase>" as true. As a a result, the widget could be hidden to screen reader users when it is displayed.

    To explain further, screen readers do not directly access the HTML; they access browser accessibility API's which interpret the HTML. Certain CSS frameworks have unusual behavior with browsers.

    This is due to a generic CSS selector that has been seen in some frameworks (where developers have incorrectly mapped aria-hidden to display:none):

    Recommendation

    • Set aria-hidden="true" where content/elements need to be hidden to assistive technology

    • Remove aria-hidden attribute where content/elements are to be made available to assistive technology, rather than set it as aria-hidden="false"

    | | 1 | 4 |

    hashtag
    44. Jurisdiction Intercept

    hashtag
    Jurisdiction Intercept Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    44.1

    Decorative SVG is not hidden from screen reader users

    Decorative SVGs were found that are not hidden from assistive technologies.

    Each US state button is made up of a SVG of the state's shape and text of the state's name. The text provides the accessible name for the button and the SVG can be considered decorative.

    Decorative images can cause confusion for screen reader users.

    Recommendation

    As with bitmap images, any decorative SVG images should be hidden from assistive technologies, so that they do not add extra verbosity when a user is navigating around a page.

    When an SVG image is used for decoration only, the attribute must be used to hide the image completely from assistive technologies. Using aria-hidden means the image will remain visible on the page.

    1.1.1 Non-text Content A

    7

    4

    44.2

    Visually grouped controls are not grouped programmatically

    hashtag
    User Journey: Pay My Bill

    hashtag
    45. Rectangular Card

    hashtag
    Rectangular Card Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    45.1

    Decorative images are not hidden from screen reader users

    Each billing option link has an adjacent decorative image. They do not convey meaningful information yet have redundant text alternatives. Having text alternative for decorative images increase the page's verbosity. This causes "page noise" for screen reader users. Navigating the site is more confusing and laborious.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    While not required, it is suggested for the link to contain the decorative image in order to increase the link's clickable area.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    5

    4

    hashtag
    46. Centered Card

    hashtag
    Centered Card Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    46.1

    Decorative images are not hidden from screen reader users

    Each card has a decorative image which does not convey meaningful information. The images have redundant and inappropriate text alternatives. Having text alternatives for decorative images increase the page's verbosity. This causes "page noise" for screen reader users. Navigating the site is more confusing and laborious.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    1.1.1 Non-text Content A

    8

    4

    hashtag
    User Journey: Search

    hashtag
    47. Search Results Page

    hashtag
    Search Results Page Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    47.1

    Decorative SVG is not hidden from screen reader users

    The buttons to go to the first page and last page of the search results contain SVGs which are not hidden from assistive technology.

    Decorative images can cause confusion for screen reader users.

    Recommendation

    As with bitmap images, any decorative SVG images should be hidden from assistive technologies, so that they do not add extra verbosity when a user is navigating around a page.

    When an SVG image is used for decoration only, the attribute must be used to hide the image completely from assistive technologies. Using aria-hidden means the image will remain visible on the page.

    It is additionally suggested to apply focusable="false"

    1.1.1 Non-text Content A

    2

    4

    47.2

    Dynamic changes in content are not announced

    hashtag
    48. Search Results Page

    hashtag
    Search Results Page Technical Details

    #

    Issue Description

    Modification

    STD

    Count

    PRIO

    48.1

    Dynamically generated search results does not trigger an announcement

    When on the search page and a search is submitted, the search results are dynamically generated but it does not trigger an announcement to assistive technology. Screen reader users must navigate from the search form control to the region below to evaluate the results. Given that a search does not trigger an announcement, screen reader users may be unaware of the change in page content.

    Recommendation

    Ensure that dynamic changes in content trigger an announcement to assistive technology.

    The best and most straightforward solution is create a live region to summarize/describe the change in search results.

    Below the search form control, there is text stating the number of results. The container for this text could be made into a live region.

    4.1.3 Status Messages AA

    1

    2

    hashtag
    Appendix: Web Accessibility Testing Tools

    · Code Validators - W3C HTML Validatorarrow-up-right or HTML Validator add-on for Firefoxarrow-up-right verifies that web pages have correct syntax

    · Browsers - use the browser's accessibility functions (e.g. text size, zoom, no page style) to determine whether the site responds as expected to those functions

    · Keyboard Testing - simply trying to use your website with the keyboard (hide your mouse) can tell you a lot about the accessibility of the site

    · Browser Accessibility Testing Add-ons and Toolbars - they extend the capability of the browsers to help you easily find accessibility issues

      • ARC Toolkitarrow-up-right

      • Accessibility Viewerarrow-up-right

    · Color Analyzer

      • Colour Contrast Analyserarrow-up-right

    · Assistive technology - tools that people with disabilities use to access the Web

      • JAWS for Windows Screen Reader desktop softwarearrow-up-right

      • NVDA free open source screen readerarrow-up-right

    Engagement Overviewarrow-up-right
    Engagement Detailsarrow-up-right
    User Journeys and Components Reviewed:arrow-up-right
    Findingsarrow-up-right
    User Journey and Component Findingsarrow-up-right
    . If you want to know more of the granular aspects and philosophy to our approach, you are encouraged to give it a read.

    Instead, this doc will mostly focus on how the dev team actually uses this implementation and a bit about our approach, as well as a proposed style guide.

    Philosophy and Approach

    Tailwind CSS is our primary way of styling within the DXT-JSS-Public React/Nuxt App. A lot of effort and resources have been put into making Tailwind and Electron do as much of the heavy lifting for us as possible, so for maintainability's sake, every effort should be made to find a solution to style your work with Tailwind. If you're hitting a wall trying to figure out an approach that works within Tailwind, don't hesitate to reach out to a teammate. If you find yourself in the rare situation where you encounter something that simply cannot be resolved using Tailwind, we use Styled Componentsarrow-up-right as our fallback. If you find that you are creating a styled component often to deal with an edge case, it's probably worth documenting herearrow-up-right.

    Tooling

    There's not a lot of tooling required for Tailwind but the TailwindCSS Intellisense VSCode pluginarrow-up-right can be pretty helpful. Once installed, it gives suggestions for Tailwind classes as well as Electron-specific ones. It also shows a preview of the CSS rules that the utility class utilizes.

    Enabling Tailwind Properties

    Although Tailwind provides the flexibility to apply a wide range of modifiers to a variety of CSS rules, you may occasionally find that a Tailwind class is not behaving the way you expect in a responsive context, or upon hover. This may mean that you will need edit the Tailwind config. You can find more info about that here.arrow-up-right Please be careful to extend the valuesarrow-up-right, rather than simply adding them to the variant object, which will overwrite all the defaults.

    Key Differences

    The main difference you'll find between Tailwind's approach and Electron's is that Duke doesn't need an extensive color library. As a result, you'll find that something like text-blue-800 or bg-yellow-200 does not behave as you'd expect. Most likely you will be looking for something like text-blue-dark or bg-yellow. So the color palette will be limited, and rather than a number to modify the color, there will either be no modifier, or a modifier of -light, -lighter, -dark, or -darker.

    Style Guide

    Because almost all of our styles exist within utility classes, there is often no need for traditional CSS classes to style a block. It's fairly unusual to need to add a class like wrapper or large BEM classes. Occasionally, you may need to add a class to make it easier for unit tests to search for a selector. In such a case, we suggest that you use a js- prefix, and that you place it at the beginning of your utility classes.

    example:

    |

    1

    |

    <``div class``=``"js-form text-blue-dark ..."``>

    |

    Often, the amount of classes you need to style a complex element can be rather long. In this case, it is suggested that you group your classes conceptually. Since Tailwind is a mobile-first framework, it makes sense to start with "base" styles that will be present across all sizes of the component. Of the base styles, start with sizing (height, width, padding, margin) and other fiddly rules so that they are easily accessible to you and anyone who may need to maintain your code in the future. Utility classes that represent rules that are easily identifiable at a glance, such as text color or background color, should come secondary.

    🚫 Bad

    |

    <div className=``"text-blue mt-12 bg-black w-20 md:flex px-24" />

    |

    ✅ Good

    |

    <div className=``"w-20 mt-12 px-24 text-blue bg-black md:flex ..." />

    |

    From there, group your classNames into responsive clusters in order from smallest to largest.

    🚫 Bad

    |

    <div className=``"lg:flex md:block xl:bg-gray-lighter lg:text-center md:text-white" />

    |

    ✅ Good

    |

    <div className=``"md:block md:text-white lg:flex lg:text-center xl:bg-gray-lighter" />

    |

    And lastly, add your non-responsive variants, such as hover states, and transitions. So ultimately, your styles should look something like this:

    |

    <div className=``"js-form w-20 mt-12 px-24 text-blue bg-black md:block md:text-white lg:flex lg:text-center xl:bg-gray-lighter hover:bg-blue transition-all duration-500" />

    |

    Resources

    Electron Docsarrow-up-right

    Tailwind VSCode Intellisense Pluginarrow-up-right

    Nerdcave Cheatsheetarrow-up-right - some random guy made a really handy cheatsheet for Tailwind CSS. Obviously, our rules won't be on it, but it's a nice quick reference for a lot of the classes.

    Tailwind CSS Docsarrow-up-right - their official docs are better than most

    chevron-rightCheatsheet MDhashtag

    Tailwind CSS Cheat Sheet

    Excerpt

    Cheat sheet that provides a quick, interactive reference for all utility classes and CSS properties provided by Tailwind CSS, a utility-first CSS framework.


    Layout

    • Breakpoints (screen sizes) that wrap utility classes.

    • Sets rendering of an element's fragments when broken across multiple lines, columns, or pages.

    • Sets max-width to match min-width of the current breakpoint.

    • Sets how the total width and height of an element is calculated.

    • Sets the display box type of an element.

    • Sets an element's placement to a side of its container and allows content to wrap around it.

    • Sets whether an element is moved below preceding floated elements.

    • Sets whether an element creates a stacking context.

    • Sets how the content of a replaced element (img or video tag) should be resized.

    • Sets the alignment of the selected replaced element.

    • Sets how to handle content that's too big for its container.

    • Sets browser behavior upon reaching the boundary of a scrolling area.

    • Sets an element's position.

    • Sets the placement of a positioned element.

    • Show or hide without affecting the layout of the document.

    • Sets the z-order ("stack order") of a positioned element.

    Spacing

    • Controls padding in 0.25rem increments.

    • Controls margin (and negative margin) in 0.25rem increments.

    • Sets left or top (x or y) margin between child elements, but skips the first element.

    Backgrounds

    • Sets behavior of background images when scrolling.

    • Sets where a background extends.

    • Sets background opacity when used with bg-[color].

    Tables

    • Collapse or separate table borders.

    • Defines the algorithm used to lay out table cells, rows, and columns.

    Transforms

    • Scales an element that has transform applied.

    • Rotates an element that has transform applied.

    • Translates an element that has transform applied.

    Effects

    • Sets the shadow around an element.

    • Sets the transparency of an element.

    • Sets how an element blends with the background.

    Flexbox

    • Sets element to be a flex container.

    • Sets direction of flex items.

    • Creates how flex items wrap.

    Sizing

    • Sets the width of an element.

    • Sets the minimum width of an element.

    • Sets the maxiumum width of an element.

    Borders

    • Sets border width in increments of 1px.

    • Sets border opacity when used with border-[color].

    • Sets left or top (x or y) border width between child elements, but skips the first element.

    Transitions and Animation

    • Sets the CSS properties affected by transition animations.

    • Sets the length of time for a transition animations to complete.

    • transition-timing-function

      Sets the easing function of transition animations.

    Interactivity

    • Disables native styling based on the operating system's theme.

    • Changes the cursor when hovering over an element.

    • Sets the outline of the element.

    SVG

    • Sets the color to paint an SVG.

    • Sets the outline color of an SVG.

    • Sets the outline width of an SVG.

    Grid

    • Defines columns for grid layout.

    • Sets a grid item size and location within the grid column.

    • Defines rows for grid layout.

    Box Alignment

    • Controls how flex items are positioned along container's main axis.

      .justify-start
      justify-content: flex-start;

    Typography

    • .text-transparent
      color: transparent;

    Filter

    • Sets blur filter on elements (use with filter utility).

    • Sets brightness filter on elements (use with filter utility).

    • Sets contrast filter on elements (use with filter utility).

    Url: https: //nerdcave.com/tailwind-cheat-sheet

    {% embed url="https: //tailwindcomponents.com/cheatsheet" %}

    Dec 10, 2020arrow-up-right
    herearrow-up-right
    , to create a set of externally-keyed children.

    The add-ons below are in the development (unminified) version of React only:

    • Perf, a performance profiling tool for finding optimization opportunities.

    • ReactTestUtils, simple helpers for writing test cases.

    hashtag
    Legacy Add-ons

    The add-ons below are considered legacy and their use is discouraged. They will keep working in observable future, but there is no further development.

    • PureRenderMixin. Use React.PureComponent instead.

    • shallowCompare, a helper function that performs a shallow comparison for props and state in a component to decide if a component should update. We recommend using React.PureComponent instead.

    • update. Use instead.

    • , pre-configured DOM factories to make React easier to use without JSX.

    hashtag
    Deprecated Add-ons

    • LinkedStateMixin has been deprecated.

    • TransitionGroup and CSSTransitionGroup have been deprecated in favor of their drop-in replacementsarrow-up-right.

    hashtag
    Using React with Add-ons

    You can install the add-ons individually from npm (e.g. npm install react-addons-create-fragment) and import them:

    When using React 15 or earlier from a CDN, you can use react-with-addons.js instead of react.js:

    The add-ons will be available via the React.addons global (e.g. React.addons.TestUtils).

    Note:

    We recommend using React Testing Libraryarrow-up-right which is designed to enable and encourage writing tests that use your components as the end users do.

    For React versions <= 16, the Enzymearrow-up-right library makes it easy to assert, manipulate, and traverse your React Components' output.

    • act()arrow-up-right

    • mockComponent()arrow-up-right

    • isElement()arrow-up-right

    hashtag
    Reference

    hashtag
    act()

    To prepare a component for assertions, wrap the code rendering it and performing updates inside an act() call. This makes your test run closer to how React works in the browser.

    Note

    If you use react-test-renderer, it also provides an act export that behaves the same way.

    For example, let's say we have this Counter component:

    Here is how we can test it:

    • Don't forget that dispatching DOM events only works when the DOM container is added to the document. You can use a library like React Testing Libraryarrow-up-right to reduce the boilerplate code.

    • The recipes document contains more details on how act() behaves, with examples and usage.


    hashtag
    mockComponent()

    Pass a mocked component module to this method to augment it with useful methods that allow it to be used as a dummy React component. Instead of rendering as usual, the component will become a simple <div> (or other tag if mockTagName is provided) containing any provided children.

    Note:

    mockComponent() is a legacy API. We recommend using jest.mock()arrow-up-right instead.


    hashtag
    isElement()

    Returns true if element is any React element.


    hashtag
    isElementOfType()

    Returns true if element is a React element whose type is of a React componentClass.


    hashtag
    isDOMComponent()

    Returns true if instance is a DOM component (such as a <div> or <span>).


    hashtag
    isCompositeComponent()

    Returns true if instance is a user-defined component, such as a class or a function.


    hashtag
    isCompositeComponentWithType()

    Returns true if instance is a component whose type is of a React componentClass.


    hashtag
    findAllInRenderedTree()

    Traverse all components in tree and accumulate all components where test(component) is true. This is not that useful on its own, but it's used as a primitive for other test utils.


    hashtag
    scryRenderedDOMComponentsWithClass()

    Finds all DOM elements of components in the rendered tree that are DOM components with the class name matching className.


    hashtag
    findRenderedDOMComponentWithClass()

    Like scryRenderedDOMComponentsWithClass()arrow-up-right but expects there to be one result, and returns that one result, or throws exception if there is any other number of matches besides one.


    hashtag
    scryRenderedDOMComponentsWithTag()

    Finds all DOM elements of components in the rendered tree that are DOM components with the tag name matching tagName.


    hashtag
    findRenderedDOMComponentWithTag()

    Like scryRenderedDOMComponentsWithTag()arrow-up-right but expects there to be one result, and returns that one result, or throws exception if there is any other number of matches besides one.


    hashtag
    scryRenderedComponentsWithType()

    Finds all instances of components with type equal to componentClass.


    hashtag
    findRenderedComponentWithType()

    Same as scryRenderedComponentsWithType()arrow-up-right but expects there to be one result and returns that one result, or throws exception if there is any other number of matches besides one.


    hashtag
    renderIntoDocument()

    Render a React element into a detached DOM node in the document. This function requires a DOM. It is effectively equivalent to:

    Note:

    You will need to have window, window.document and window.document.createElement globally available before you import React. Otherwise React will think it can't access the DOM and methods like setState won't work.


    hashtag
    Other Utilities

    hashtag
    Simulate

    Simulate an event dispatch on a DOM node with optional eventData event data.

    Simulate has a method for every event that React understands.

    Clicking an element

    Changing the value of an input field and then pressing ENTER.

    Note

    You will have to provide any event property that you're using in your component (e.g. keyCode, which, etc...) as React is not creating any of these for you.


    Jestarrow-up-right
    React Tutorialarrow-up-right

    Concurrent Mode API Reference (Experimental)

    .scary > blockquote { background-color: rgba(237, 51, 21, 0.2); border-left-color: #ed3315; }

    Caution:

    This page was about experimental features that aren't yet available in a stable release. It was aimed at early adopters and people who are curious.

    Much of the information on this page is now outdated and exists only for archival purposes. Please refer to the React 18 Alpha announcement post for the up-to-date information.

    Before React 18 is released, we will replace this page with stable documentation.

    This page is an API reference for the React Concurrent Mode. If you're looking for a guided introduction instead, check out Concurrent UI Patterns.

    Note: This is a Community Preview and not the final stable version. There will likely be future changes to these APIs. Use at your own risk!

    hashtag
    Enabling Concurrent Mode

    hashtag
    createRoot

    Replaces ReactDOM.render(<App />, rootNode) and enables Concurrent Mode.

    For more information on Concurrent Mode, check out the Concurrent Mode documentation.

    hashtag
    Suspense API

    hashtag
    Suspense

    Suspense lets your components "wait" for something before they can render, showing a fallback while waiting.

    In this example, ProfileDetails is waiting for an asynchronous API call to fetch some data. While we wait for ProfileDetails and ProfilePhoto, we will show the Loading... fallback instead. It is important to note that until all children inside <Suspense> have loaded, we will continue to show the fallback.

    Suspense takes two props:

    • fallback takes a loading indicator. The fallback is shown until all of the children of the Suspense component have finished rendering.

    • unstable_avoidThisFallback takes a boolean. It tells React whether to "skip" revealing this boundary during the initial load. This API will likely be removed in a future release.

    hashtag
    <SuspenseList>

    SuspenseList helps coordinate many components that can suspend by orchestrating the order in which these components are revealed to the user.

    When multiple components need to fetch data, this data may arrive in an unpredictable order. However, if you wrap these items in a SuspenseList, React will not show an item in the list until previous items have been displayed (this behavior is adjustable).

    SuspenseList takes two props:

    • revealOrder (forwards, backwards, together) defines the order in which the SuspenseList children should be revealed.

      • together reveals all of them when they're ready instead of one by one.

    Note that SuspenseList only operates on the closest Suspense and SuspenseList components below it. It does not search for boundaries deeper than one level. However, it is possible to nest multiple SuspenseList components in each other to build grids.

    hashtag
    useTransition

    useTransition allows components to avoid undesirable loading states by waiting for content to load before transitioning to the next screen. It also allows components to defer slower, data fetching updates until subsequent renders so that more crucial updates can be rendered immediately.

    The useTransition hook returns two values in an array.

    • startTransition is a function that takes a callback. We can use it to tell React which state we want to defer.

    • isPending is a boolean. It's React's way of informing us whether we're waiting for the transition to finish.

    If some state update causes a component to suspend, that state update should be wrapped in a transition.

    In this code, we've wrapped our data fetching with startTransition. This allows us to start fetching the profile data right away, while deferring the render of the next profile page and its associated Spinner for 2 seconds (the time shown in timeoutMs).

    The isPending boolean lets React know that our component is transitioning, so we are able to let the user know this by showing some loading text on the previous profile page.

    For an in-depth look at transitions, you can read Concurrent UI Patterns.

    useTransition Config

    useTransition accepts an optional Suspense Config with a timeoutMs. This timeout (in milliseconds) tells React how long to wait before showing the next state (the new Profile Page in the above example).

    Note: We recommend that you share Suspense Config between different modules.

    hashtag
    useDeferredValue

    Returns a deferred version of the value that may "lag behind" it for at most timeoutMs.

    This is commonly used to keep the interface responsive when you have something that renders immediately based on user input and something that needs to wait for a data fetch.

    A good example of this is a text input.

    This allows us to start showing the new text for the input immediately, which allows the webpage to feel responsive. Meanwhile, MySlowList "lags behind" for up to 2 seconds according to the timeoutMs before updating, allowing it to render with the current text in the background.

    For an in-depth look at deferring values, you can read Concurrent UI Patterns.

    useDeferredValue Config

    useDeferredValue accepts an optional Suspense Config with a timeoutMs. This timeout (in milliseconds) tells React how long the deferred value is allowed to lag behind.

    React will always try to use a shorter lag when network and device allows it.

    React and Typescript: Build a Portfolio ProjectUdemychevron-right

    Styling and CSS

    hashtag
    How do I add CSS classes to components?

    Pass a string as the className prop:

    It is common for CSS classes to depend on the component props or state:

    Tip

    If you often find yourself writing code like this, package can simplify it.

    hashtag
    Can I use inline styles?

    Yes, see the docs on styling here.

    hashtag
    Are inline styles bad?

    CSS classes are generally better for performance than inline styles.

    hashtag
    What is CSS-in-JS?

    "CSS-in-JS" refers to a pattern where CSS is composed using JavaScript instead of defined in external files.

    Note that this functionality is not a part of React, but provided by third-party libraries. React does not have an opinion about how styles are defined; if in doubt, a good starting point is to define your styles in a separate *.css file as usual and refer to them using className.

    hashtag
    Can I do animations in React?

    React can be used to power animations. See , , , or , for example.

    Fragments

    A common pattern in React is for a component to return multiple elements. Fragments let you group a list of children without adding extra nodes to the DOM.

    There is also a new short syntaxarrow-up-right for declaring them.

    hashtag
    Motivation

    A common pattern is for a component to return a list of children. Take this example React snippet:

    <Columns /> would need to return multiple <td> elements in order for the rendered HTML to be valid. If a parent div was used inside the render() of <Columns />, then the resulting HTML will be invalid.

    results in a <Table /> output of:

    Fragments solve this problem.

    hashtag
    Usage

    which results in a correct <Table /> output of:

    hashtag
    Short Syntax

    There is a new, shorter syntax you can use for declaring fragments. It looks like empty tags:

    You can use <></> the same way you'd use any other element except that it doesn't support keys or attributes.

    hashtag
    Keyed Fragments

    Fragments declared with the explicit <React.Fragment> syntax may have keys. A use case for this is mapping a collection to an array of fragments -- for example, to create a description list:

    key is the only attribute that can be passed to Fragment. In the future, we may add support for additional attributes, such as event handlers.

    hashtag
    Live Demo

    You can try out the new JSX fragment syntax with this .

    Arbitrary Values

    hashtag
    Adding Custom Styles - Tailwind CSS

    Excerpt

    Best practices for adding your own custom styles to Tailwind.


    Often the biggest challenge when working with a framework is figuring out what you’re supposed to do when there’s something you need that the framework doesn’t handle for you.

    Tailwind has been designed from the ground up to be extensible and customizable, so that no matter what you’re building you never feel like you’re fighting the framework.

    This guide covers topics like customizing your design tokens, how to break out of those constraints when necessary, adding your own custom CSS, and extending the framework with plugins.

    hashtag
    Customizing your theme

    If you want to change things like your color palette, spacing scale, typography scale, or breakpoints, add your customizations to the theme section of your tailwind.config.js file:

    Learn more about customizing your theme in the documentation.


    hashtag
    Using arbitrary values

    While you can usually build the bulk of a well-crafted design using a constrained set of design tokens, once in a while you need to break out of those constraints to get things pixel-perfect.

    When you find yourself really needing something like top: 117px to get a background image in just the right spot, use Tailwind’s square bracket notation to generate a class on the fly with any arbitrary value:

    This is basically like inline styles, with the major benefit that you can combine it with interactive modifiers like hover and responsive modifiers like lg:

    This works for everything in the framework, including things like background colors, font sizes, pseudo-element content, and more:

    hashtag
    Arbitrary properties

    If you ever need to use a CSS property that Tailwind doesn’t include a utility for out of the box, you can also use square bracket notation to write completely arbitrary CSS:

    This is really like inline styles, but again with the benefit that you can use modifiers:

    This can be useful for things like CSS variables as well, especially when they need to change under different conditions:

    hashtag
    Handling whitespace

    When an arbitrary value needs to contain a space, use an underscore (_) instead and Tailwind will automatically convert it to a space at build-time:

    In situations where underscores are common but spaces are invalid, Tailwind will preserve the underscore instead of converting it to a space, for example in URLs:

    In the rare case that you actually need to use an underscore but it’s ambiguous because a space is valid as well, escape the underscore with a backslash and Tailwind won’t convert it to a space:

    hashtag
    Resolving ambiguities

    Many utilities in Tailwind share a common namespace but map to different CSS properties. For example text-lg and text-black both share the text- namespace, but one is for font-size and the other is for color.

    When using arbitrary values, Tailwind can generally handle this ambiguity automatically based on the value you pass in:

    Sometimes it really is ambiguous though, for example when using CSS variables:

    In these situations, you can “hint” the underlying type to Tailwind by adding a before the value:


    hashtag
    Using CSS and @layer

    When you need to add truly custom CSS rules to a Tailwind project, the easiest approach is to just add the custom CSS to your stylesheet:

    For more power, you can also use the @layer directive to add styles to Tailwind’s base, components, and utilities layers:

    Why does Tailwind group styles into "layers"?

    In CSS, the order of the rules in your stylesheet decides which declaration wins when two selectors have the same specificity:

    Here, both buttons will be black since .bg-black comes after .btn in the CSS:

    To manage this, Tailwind organizes the styles it generates into three different “layers” — a concept popularized by .

    • The base layer is for things like reset rules or default styles applied to plain HTML elements.

    • The components layer is for class-based styles that you want to be able to override with utilities.

    • The utilities

    Being explicit about this makes it easier to understand how your styles will interact with each other, and using the @layer directive lets you control the final declaration order while still organizing your actual code in whatever way you like.

    The @layer directive helps you control declaration order by automatically relocating your styles to the corresponding @tailwind directive, and also enables features like and for your own custom CSS.

    hashtag
    Adding base styles

    If you just want to set some defaults for the page (like the text color, background color, or font family), the easiest option is just adding some classes to the html or body elements:

    This keeps your base styling decisions in your markup alongside all of your other styles, instead of hiding them in a separate file.

    If you want to add your own default base styles for specific HTML elements, use the @layer directive to add those styles to Tailwind’s base layer:

    Use the function or directive when adding custom base styles if you want to refer to any of the values defined in your .

    hashtag
    Adding component classes

    Use the components layer for any more complicated classes you want to add to your project that you’d still like to be able to override with utility classes.

    Traditionally these would be classes like card, btn, badge — that kind of thing.

    By defining component classes in the components layer, you can still use utility classes to override them when necessary:

    Using Tailwind you probably don’t need these types of classes as often as you think. Read our guide on for our recommendations.

    The components layer is also a good place to put custom styles for any third-party components you’re using:

    Use the function or directive when adding custom component styles if you want to refer to any of the values defined in your .

    hashtag
    Adding custom utilities

    Add any of your own custom utility classes to Tailwind’s utilities layer:

    This can be useful when there’s a CSS feature you’d like to use in your project that Tailwind doesn’t include utilities for out of the box.

    hashtag
    Using modifiers with custom CSS

    Any custom styles you add to Tailwind with @layer will automatically support Tailwind’s modifier syntax for handling things like hover states, responsive breakpoints, dark mode, and more.

    Learn more about how these modifiers work in the documentation.

    hashtag
    Removing unused custom CSS

    Any custom styles you add to the base, components, or utilities layers will only be included in your compiled CSS if those styles are actually used in your HTML.

    If you want to add some custom CSS that should always be included, add it to your stylesheet without using the @layer directive:

    Make sure to put your custom styles where they need to go to get the precedence behavior you want. In the example above, we’ve added the .card class before @tailwind utilities to make sure utilities can still override it.

    hashtag
    Using multiple CSS files

    If you are writing a lot of CSS and organizing it into multiple files, make sure those files are combined into a single stylesheet before processing them with Tailwind, or you’ll see errors about using @layer without the corresponding @tailwind directive.

    The easiest way to do this is using the plugin:

    Learn more in our documentation.

    hashtag
    Layers and per-component CSS

    Component frameworks like Vue and Svelte support adding per-component styles within a <style> block that lives in each component file.

    While you can use features like @apply and theme inside component styles like this, the @layer directive will not work and you’ll see an error about @layer being used without a matching @tailwind directive:

    Don't use `@layer` in component styles

    This is because under-the-hood, frameworks like Vue and Svelte are processing every single <style> block independently, and running your PostCSS plugin chain against each one in isolation.

    That means if you have 10 components that each have a <style> block, Tailwind is being run 10 separate times, and each run has zero knowledge about the other runs. Because of this, Tailwind can’t take the styles you define in a @layer and move them to the corresponding @tailwind directive, because as far as Tailwind can tell there is no @tailwind directive to move it to.

    One solution to this is to simply not use @layer inside your component styles:

    Add your styles without using `@layer`

    You lose the ability to control the precedence of your styles, but unfortunately that’s totally out of our control because of how these tools work.

    Our recommendation is that you just don’t use component styles like this at all and instead use Tailwind the way it’s intended to be used — as a single global stylesheet where you use the classes directly in your HTML:

    Use Tailwind's utilities instead of component styles


    hashtag
    Writing plugins

    You can also add custom styles to your project using Tailwind’s plugin system instead of using a CSS file:

    Learn more about writing your own plugins in the documentation.

    Components and Props

    Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. This page provides an introduction to the idea of components. You can find a detailed component API reference here.

    Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called "props") and return React elements describing what should appear on the screen.

    hashtag
    Function and Class Components

    The simplest way to define a component is to write a JavaScript function:

    This function is a valid React component because it accepts a single "props" (which stands for properties) object argument with data and returns a React element. We call such components "function components" because they are literally JavaScript functions.

    You can also use an to define a component:

    The above two components are equivalent from React's point of view.

    Function and Class components both have some additional features that we will discuss in the next sections.

    hashtag
    Rendering a Component

    Previously, we only encountered React elements that represent DOM tags:

    However, elements can also represent user-defined components:

    When React sees an element representing a user-defined component, it passes JSX attributes and children to this component as a single object. We call this object "props".

    For example, this code renders "Hello, Sara" on the page:

    Let's recap what happens in this example:

    1. We call ReactDOM.render() with the <Welcome name="Sara" /> element.

    2. React calls the Welcome component with {name: 'Sara'} as the props.

    Note: Always start component names with a capital letter.

    React treats components starting with lowercase letters as DOM tags. For example, <div /> represents an HTML div tag, but <Welcome /> represents a component and requires Welcome to be in scope.

    To learn more about the reasoning behind this convention, please read JSX In Depth.

    hashtag
    Composing Components

    Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail. A button, a form, a dialog, a screen: in React apps, all those are commonly expressed as components.

    For example, we can create an App component that renders Welcome many times:

    Typically, new React apps have a single App component at the very top. However, if you integrate React into an existing app, you might start bottom-up with a small component like Button and gradually work your way to the top of the view hierarchy.

    hashtag
    Extracting Components

    Don't be afraid to split components into smaller components.

    For example, consider this Comment component:

    It accepts author (an object), text (a string), and date (a date) as props, and describes a comment on a social media website.

    This component can be tricky to change because of all the nesting, and it is also hard to reuse individual parts of it. Let's extract a few components from it.

    First, we will extract Avatar:

    The Avatar doesn't need to know that it is being rendered inside a Comment. This is why we have given its prop a more generic name: user rather than author.

    We recommend naming props from the component's own point of view rather than the context in which it is being used.

    We can now simplify Comment a tiny bit:

    Next, we will extract a UserInfo component that renders an Avatar next to the user's name:

    This lets us simplify Comment even further:

    Extracting components might seem like grunt work at first, but having a palette of reusable components pays off in larger apps. A good rule of thumb is that if a part of your UI is used several times (Button, Panel, Avatar), or is complex enough on its own (App, FeedStory, Comment), it is a good candidate to be extracted to a separate component.

    hashtag
    Props are Read-Only

    Whether you declare a component , it must never modify its own props. Consider this sum function:

    Such functions are called because they do not attempt to change their inputs, and always return the same result for the same inputs.

    In contrast, this function is impure because it changes its own input:

    React is pretty flexible but it has a single strict rule:

    All React components must act like pure functions with respect to their props.

    Of course, application UIs are dynamic and change over time. In the next section, we will introduce a new concept of "state". State allows React components to change their output over time in response to user actions, network responses, and anything else, without violating this rule.

    Animation Add-Ons

    Note:

    ReactTransitionGroup and ReactCSSTransitionGroup have been moved to the react-transition-grouparrow-up-right package that is maintained by the community. Its 1.x branch is completely API-compatible with the existing addons. Please file bugs and feature requests in the new repositoryarrow-up-right.

    The ReactTransitionGrouparrow-up-right add-on component is a low-level API for animation, and ReactCSSTransitionGrouparrow-up-right is an add-on component for easily implementing basic CSS animations and transitions.

    hashtag
    High-level API: ReactCSSTransitionGroup

    ReactCSSTransitionGroup is a high-level API based on and is an easy way to perform CSS transitions and animations when a React component enters or leaves the DOM. It's inspired by the excellent library.

    Importing

    Note:

    You must provide the key attribute for all children of ReactCSSTransitionGroup, even when only rendering a single item. This is how React will determine which children have entered, left, or stayed.

    In this component, when a new item is added to ReactCSSTransitionGroup it will get the example-enter CSS class and the example-enter-active CSS class added in the next tick. This is a convention based on the transitionName prop.

    You can use these classes to trigger a CSS animation or transition. For example, try adding this CSS and adding a new list item:

    You'll notice that animation durations need to be specified in both the CSS and the render method; this tells React when to remove the animation classes from the element and -- if it's leaving -- when to remove the element from the DOM.

    hashtag
    Animate Initial Mounting

    ReactCSSTransitionGroup provides the optional prop transitionAppear, to add an extra transition phase at the initial mount of the component. There is generally no transition phase at the initial mount as the default value of transitionAppear is false. The following is an example which passes the prop transitionAppear with the value true.

    During the initial mount ReactCSSTransitionGroup will get the example-appear CSS class and the example-appear-active CSS class added in the next tick.

    At the initial mount, all children of the ReactCSSTransitionGroup will appear but not enter. However, all children later added to an existing ReactCSSTransitionGroup will enter but not appear.

    Note:

    The prop transitionAppear was added to ReactCSSTransitionGroup in version 0.13. To maintain backwards compatibility, the default value is set to false.

    However, the default values of transitionEnter and transitionLeave are true so you must specify transitionEnterTimeout and transitionLeaveTimeout

    hashtag
    Custom Classes

    It is also possible to use custom class names for each of the steps in your transitions. Instead of passing a string into transitionName you can pass an object containing either the enter and leave class names, or an object containing the enter, enter-active, leave-active, and leave class names. If only the enter and leave classes are provided, the enter-active and leave-active classes will be determined by appending '-active' to the end of the class name. Here are two examples using custom classes:

    hashtag
    Animation Group Must Be Mounted To Work

    In order for it to apply transitions to its children, the ReactCSSTransitionGroup must already be mounted in the DOM or the prop transitionAppear must be set to true.

    The example below would not work, because the ReactCSSTransitionGroup is being mounted along with the new item, instead of the new item being mounted within it. Compare this to the section above to see the difference.

    hashtag
    Animating One or Zero Items

    In the example above, we rendered a list of items into ReactCSSTransitionGroup. However, the children of ReactCSSTransitionGroup can also be one or zero items. This makes it possible to animate a single element entering or leaving. Similarly, you can animate a new element replacing the current element. For example, we can implement a simple image carousel like this:

    hashtag
    Disabling Animations

    You can disable animating enter or leave animations if you want. For example, sometimes you may want an enter animation and no leave animation, but ReactCSSTransitionGroup waits for an animation to complete before removing your DOM node. You can add transitionEnter={false} or transitionLeave={false} props to ReactCSSTransitionGroup to disable these animations.

    Note:

    When using ReactCSSTransitionGroup, there's no way for your components to be notified when a transition has ended or to perform any more complex logic around animation. If you want more fine-grained control, you can use the lower-level ReactTransitionGroup API which provides the hooks you need to do custom transitions.


    hashtag
    Low-level API: ReactTransitionGroup

    Importing

    ReactTransitionGroup is the basis for animations. When children are declaratively added or removed from it (as in the ), special lifecycle methods are called on them.

    Rendering a Different Component

    ReactTransitionGroup renders as a span by default. You can change this behavior by providing a component prop. For example, here's how you would render a <ul>:

    Any additional, user-defined, properties will become properties of the rendered component. For example, here's how you would render a <ul> with CSS class:

    Every DOM component that React can render is available for use. However, component does not need to be a DOM component. It can be any React component you want; even ones you've written yourself! Just write component={List} and your component will receive this.props.children.

    Rendering a Single Child

    People often use ReactTransitionGroup to animate mounting and unmounting of a single child such as a collapsible panel. Normally ReactTransitionGroup wraps all its children in a span (or a custom component as described above). This is because any React component has to return a single root element, and ReactTransitionGroup is no exception to this rule.

    However if you only need to render a single child inside ReactTransitionGroup, you can completely avoid wrapping it in a <span> or any other DOM component. To do this, create a custom component that renders the first child passed to it directly:

    Now you can specify FirstChild as the component prop in <ReactTransitionGroup> props and avoid any wrappers in the result DOM:

    This only works when you are animating a single child in and out, such as a collapsible panel. This approach wouldn't work when animating multiple children or replacing the single child with another child, such as an image carousel. For an image carousel, while the current image is animating out, another image will animate in, so <ReactTransitionGroup> needs to give them a common DOM parent. You can't avoid the wrapper for multiple children, but you can customize the wrapper with the component prop as described above.


    hashtag
    Reference

    hashtag
    componentWillAppear()

    This is called at the same time as componentDidMount() for components that are initially mounted in a TransitionGroup. It will block other animations from occurring until callback is called. It is only called on the initial render of a TransitionGroup.


    hashtag
    componentDidAppear()

    This is called after the callback function that was passed to componentWillAppear is called.


    hashtag
    componentWillEnter()

    This is called at the same time as componentDidMount() for components added to an existing TransitionGroup. It will block other animations from occurring until callback is called. It will not be called on the initial render of a TransitionGroup.


    hashtag
    componentDidEnter()

    This is called after the callback function that was passed to is called.


    hashtag
    componentWillLeave()

    This is called when the child has been removed from the ReactTransitionGroup. Though the child has been removed, ReactTransitionGroup will keep it in the DOM until callback is called.


    hashtag
    componentDidLeave()

    This is called when the willLeave callback is called (at the same time as componentWillUnmount()).

    Lists and Keys

    First, let's review how you transform lists in JavaScript.

    Given the code below, we use the function to take an array of numbers and double their values. We assign the new array returned by map() to the variable doubled and log it:

    This code logs [2, 4, 6, 8, 10] to the console.

    In React, transforming arrays into lists of elements is nearly identical.

    JSX In Depth

    Fundamentally, JSX just provides syntactic sugar for the React.createElement(component, props, ...children) function. The JSX code:

    compiles into:

    You can also use the self-closing form of the tag if there are no children. So:

    compiles into:

    If you want to test out how some specific JSX is converted into JavaScript, you can try out .

    Using the Effect Hook

    Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

    The Effect Hook lets you perform side effects in function components:

    This snippet is based on the counter example from the previous page, but we added a new feature to it: we set the document title to a custom message including the number of clicks.

    Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you're used to calling these operations "side effects" (or just "effects"), you've likely performed them in your components before.

    Tip

    const btn = document.querySelector('button');
    
    btn.addEventListener('click', () => {
      alert("hi!");
    });
    <button
      type="button"
      onClick={() => alert("hi!")}
    >
      Say hi!
    </button>
    function handleSubmit(e) {
      e.preventDefault();
      alert('Hello, world!');
    }
    <form onSubmit={handleSubmit}>
    function addTask(name) {
      alert(name);
    }
    <Form addTask={addTask} />
    function handleSubmit(e) {
      e.preventDefault();
      props.addTask("Say hello!");
    }
    import React, { useState } from "react";
    const [name, setName] = useState('Use hooks!');
    <input
      type="text"
      id="new-todo-input"
      className="input input__lg"
      name="text"
      autoComplete="off"
      value={name}
    />
    const [name, setName] = useState('');
    // near the top of the `Form` component
    function handleChange(e) {
      console.log("Typing!");
    }
    
    // Down in the return statement
    <input
      type="text"
      id="new-todo-input"
      className="input input__lg"
      name="text"
      autoComplete="off"
      value={name}
      onChange={handleChange}
    />
    function handleChange(e) {
      console.log(e.target.value);
    }
    function handleChange(e) {
      setName(e.target.value);
    }
    function handleSubmit(e) {
      e.preventDefault();
      props.addTask(name);
      setName("");
    }
    import React, { useState } from "react";
    
    function Form(props) {
      const [name, setName] = useState("");
    
      function handleChange(e) {
        setName(e.target.value);
      }
    
      function handleSubmit(e) {
        e.preventDefault();
        props.addTask(name);
        setName("");
      }
      return (
        <form onSubmit={handleSubmit}>
          <h2 className="label-wrapper">
            <label htmlFor="new-todo-input" className="label__lg">
              What needs to be done?
            </label>
          </h2>
          <input
            type="text"
            id="new-todo-input"
            className="input input__lg"
            name="text"
            autoComplete="off"
            value={name}
            onChange={handleChange}
          />
          <button type="submit" className="btn btn__primary btn__lg">
            Add
          </button>
        </form>
      );
    }
    
    export default Form;
    import React, { useState } from "react";
    const [tasks, setTasks] = useState(props.tasks);
    const taskList = tasks.map(task => (
        <Todo
            id={task.id}
            name={task.name}
            completed={task.completed}
            key={task.id}
          />
        )
      );
    function addTask(name) {
      const newTask = { id: "id", name: name, completed: false };
      setTasks([...tasks, newTask]);
    }
    import { nanoid } from "nanoid";
    const newTask = { id: "todo-" + nanoid(), name: name, completed: false };
    const headingText = `${taskList.length} tasks remaining`;
    const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task';
    const headingText = `${taskList.length} ${tasksNoun} remaining`;
    <h2 id="list-heading">{headingText}</h2>
    function toggleTaskCompleted(id) {
      console.log(tasks[0])
    }
    const taskList = tasks.map(task => (
      <Todo
          id={task.id}
          name={task.name}
          completed={task.completed}
          key={task.id}
          toggleTaskCompleted={toggleTaskCompleted}
      />
    ));
    <input
      id={props.id}
      type="checkbox"
      defaultChecked={props.completed}
      onChange={() => props.toggleTaskCompleted(props.id)}
    />
    Object { id: "task-0", name: "Eat", completed: true }
    function toggleTaskCompleted(id) {
      const updatedTasks = tasks.map(task => {
        // if this task has the same ID as the edited task
        if (id === task.id) {
          // use object spread to make a new object
          // whose `completed` prop has been inverted
          return {...task, completed: !task.completed}
        }
        return task;
      });
      setTasks(updatedTasks);
    }
    function deleteTask(id) {
      console.log(id)
    }
    const taskList = tasks.map(task => (
      <Todo
        id={task.id}
        name={task.name}
        completed={task.completed}
        key={task.id}
        toggleTaskCompleted={toggleTaskCompleted}
        deleteTask={deleteTask}
      />
    ));
    <button
      type="button"
      className="btn btn__danger"
      onClick={() => props.deleteTask(props.id)}
    >
      Delete <span className="visually-hidden">{props.name}</span>
    </button>
    function deleteTask(id) {
      const remainingTasks = tasks.filter(task => id !== task.id);
      setTasks(remainingTasks);
    }
    <svg version="1.1" aria-hidden="true"
    focusable="false">
    <h2 id="dialog-title">Sign In</h2>
    * Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply `aria-hidden="true"` to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the `aria-hidden` attribute from page content when the dialog is closed.
    **Resources**
    <input name="email" type="text" value="" class="form-input" id="form-email" placeholder="Email Address" required="" autocomplete="email">
    <select name="PublicState" class="form-select" id="form-state" required="" autocomplete="address-level1">
    <input name="email" type="text" value="test@" class="form-input form-error" id="form-email" placeholder="Email Address" required="" aria-describedby="form-email-error-message">
    <div class="form-error-message is-visible" id="form-email-error-message"_>Enter an email address</div>
    * Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply `aria-hidden="true"` to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the `aria-hidden` attribute from page content when the dialog is closed.
    **Resources**
    import createFragment from "react-addons-create-fragment"; // ES6
    var createFragment = require("react-addons-create-fragment"); // ES5 with npm
    <script src="https://unpkg.com/react@15/dist/react-with-addons.js"></script>
    import ReactTestUtils from "react-dom/test-utils"; // ES6
    var ReactTestUtils = require("react-dom/test-utils"); // ES5 with npm
    class Counter extends React.Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
        this.handleClick = this.handleClick.bind(this);
      }
      componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
      }
      componentDidUpdate() {
        document.title = `You clicked ${this.state.count} times`;
      }
      handleClick() {
        this.setState((state) => ({
          count: state.count + 1,
        }));
      }
      render() {
        return (
          <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={this.handleClick}>Click me</button>
          </div>
        );
      }
    }
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { act } from 'react-dom/test-utils';
    import Counter from './Counter';
    
    let container;
    
    beforeEach(() => {
      container = document.createElement('div');
      document.body.appendChild(container);
    });
    
    afterEach(() => {
      document.body.removeChild(container);
      container = null;
    });
    
    it('can render and update a counter', () => {
      // Test first render and componentDidMount
      act(() => {
        ReactDOM.render(<Counter />, container);
      });
      const button = container.querySelector('button');
      const label = container.querySelector('p');
      expect(label.textContent).toBe('You clicked 0 times');
      expect(document.title).toBe('You clicked 0 times');
    
      // Test second render and componentDidUpdate
      act(() => {
        button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
      });
      expect(label.textContent).toBe('You clicked 1 times');
      expect(document.title).toBe('You clicked 1 times');
    });
    mockComponent(componentClass, [mockTagName]);
    isElement(element);
    isElementOfType(element, componentClass);
    isDOMComponent(instance);
    isCompositeComponent(instance);
    isCompositeComponentWithType(instance, componentClass);
    findAllInRenderedTree(tree, test);
    scryRenderedDOMComponentsWithClass(tree, className);
    findRenderedDOMComponentWithClass(tree, className);
    scryRenderedDOMComponentsWithTag(tree, tagName);
    findRenderedDOMComponentWithTag(tree, tagName);
    scryRenderedComponentsWithType(tree, componentClass);
    findRenderedComponentWithType(tree, componentClass);
    renderIntoDocument(element);
    const domContainer = document.createElement("div");
    ReactDOM.render(element, domContainer);
    Simulate.{eventName}(
      element,
      [eventData]
    )
    // <button ref={(node) => this.button = node}>...</button>
    const node = this.button;
    ReactTestUtils.Simulate.click(node);
    // <input ref={(node) => this.textInput = node} />
    const node = this.textInput;
    node.value = "giraffe";
    ReactTestUtils.Simulate.change(node);
    ReactTestUtils.Simulate.keyDown(node, { key: "Enter", keyCode: 13, which: 13 });
    render() {
      return <span className="menu navigation-menu">Menu</span>
    }
    render() {
      let className = 'menu';
      if (this.props.isActive) {
        className += ' menu-active';
      }
      return <span className={className}>Menu</span>
    }
    render() {
      return (
        <React.Fragment>
          <ChildA />
          <ChildB />
          <ChildC />
        </React.Fragment>
      );
    }
    class Table extends React.Component {
      render() {
        return (
          <table>
            <tr>
              <Columns />
            </tr>
          </table>
        );
      }
    }
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    array destructuringarrow-up-right
    kolodny/immutability-helperarrow-up-right
    ReactDOMFactoriesarrow-up-right
    isElementOfType()arrow-up-right
    isDOMComponent()arrow-up-right
    isCompositeComponent()arrow-up-right
    isCompositeComponentWithType()arrow-up-right
    findAllInRenderedTree()arrow-up-right
    scryRenderedDOMComponentsWithClass()arrow-up-right
    findRenderedDOMComponentWithClass()arrow-up-right
    scryRenderedDOMComponentsWithTag()arrow-up-right
    findRenderedDOMComponentWithTag()arrow-up-right
    scryRenderedComponentsWithType()arrow-up-right
    findRenderedComponentWithType()arrow-up-right
    renderIntoDocument()arrow-up-right
    Simulatearrow-up-right
    classnamesarrow-up-right
    React Transition Grouparrow-up-right
    React Motionarrow-up-right
    React Springarrow-up-right
    Framer Motionarrow-up-right
    https://jiraprod.duke-energy.com/browse/DNT-2423jiraprod.duke-energy.comchevron-right

    Suspensearrow-up-right

  • SuspenseListarrow-up-right

  • useTransitionarrow-up-right

  • useDeferredValuearrow-up-right

  • tail (collapsed, hidden) dictates how unloaded items in a SuspenseList is shown.
    • By default, SuspenseList will show all fallbacks in the list.

    • collapsed shows only the next fallback in the list.

    • hidden doesn't show any unloaded items.

    Enabling Concurrent Modearrow-up-right
    createRootarrow-up-right
    Suspensearrow-up-right
    CodePenarrow-up-right
    layer is for small, single-purpose classes that should always take precedence over any other styles.
    Theme Configurationarrow-up-right
    CSS data typearrow-up-right
    ITCSSarrow-up-right
    modifiersarrow-up-right
    tree-shakingarrow-up-right
    themearrow-up-right
    @applyarrow-up-right
    themearrow-up-right
    Reusing Stylesarrow-up-right
    themearrow-up-right
    @applyarrow-up-right
    themearrow-up-right
    Hover, Focus, and Other Statesarrow-up-right
    postcss-importarrow-up-right
    build-time importsarrow-up-right
    Pluginsarrow-up-right
    Our Welcome component returns a <h1>Hello, Sara</h1> element as the result.
  • React DOM efficiently updates the DOM to match <h1>Hello, Sara</h1>.

  • ES6 classarrow-up-right
    Try it on CodePenarrow-up-right
    Try it on CodePenarrow-up-right
    Try it on CodePenarrow-up-right
    Try it on CodePenarrow-up-right
    as a function or a classarrow-up-right
    "pure"arrow-up-right
    by default. If you don't need either enter or leave animations, pass
    transitionEnter={false}
    or
    transitionLeave={false}
    .
    componentDidEnter()arrow-up-right
  • componentWillLeave()arrow-up-right

  • componentDidLeave()arrow-up-right

  • ReactTransitionGrouparrow-up-right
    ng-animatearrow-up-right
    Getting Startedarrow-up-right
    example abovearrow-up-right
    componentWillAppear()arrow-up-right
    componentDidAppear()arrow-up-right
    componentWillEnter()arrow-up-right
    componentWillEnter()arrow-up-right
    hashtag
    Rendering Multiple Components

    You can build collections of elements and include them in JSX using curly braces {}.

    Below, we loop through the numbers array using the JavaScript map()arrow-up-right function. We return a <li> element for each item. Finally, we assign the resulting array of elements to listItems:

    We include the entire listItems array inside a <ul> element, and render it to the DOM:

    Try it on CodePenarrow-up-right

    This code displays a bullet list of numbers between 1 and 5.

    hashtag
    Basic List Component

    Usually you would render lists inside a component.

    We can refactor the previous example into a component that accepts an array of numbers and outputs a list of elements.

    When you run this code, you'll be given a warning that a key should be provided for list items. A "key" is a special string attribute you need to include when creating lists of elements. We'll discuss why it's important in the next section.

    Let's assign a key to our list items inside numbers.map() and fix the missing key issue.

    Try it on CodePenarrow-up-right

    hashtag
    Keys

    Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity:

    The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys:

    When you don't have stable IDs for rendered items, you may use the item index as a key as a last resort:

    We don't recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. Check out Robin Pokorny's article for an in-depth explanation on the negative impacts of using an index as a keyarrow-up-right. If you choose not to assign an explicit key to list items then React will default to using indexes as keys.

    Here is an in-depth explanation about why keys are necessary if you're interested in learning more.

    hashtag
    Extracting Components with Keys

    Keys only make sense in the context of the surrounding array.

    For example, if you extract a ListItem component, you should keep the key on the <ListItem /> elements in the array rather than on the <li> element in the ListItem itself.

    Example: Incorrect Key Usage

    Example: Correct Key Usage

    Try it on CodePenarrow-up-right

    A good rule of thumb is that elements inside the map() call need keys.

    hashtag
    Keys Must Only Be Unique Among Siblings

    Keys used within arrays should be unique among their siblings. However, they don't need to be globally unique. We can use the same keys when we produce two different arrays:

    Try it on CodePenarrow-up-right

    Keys serve as a hint to React but they don't get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name:

    With the example above, the Post component can read props.id, but not props.key.

    hashtag
    Embedding map() in JSX

    In the examples above we declared a separate listItems variable and included it in JSX:

    JSX allows embedding any expression in curly braces so we could inline the map() result:

    Try it on CodePenarrow-up-right

    Sometimes this results in clearer code, but this style can also be abused. Like in JavaScript, it is up to you to decide whether it is worth extracting a variable for readability. Keep in mind that if the map() body is too nested, it might be a good time to extract a component.

    map()arrow-up-right
    hashtag
    Specifying The React Element Type

    The first part of a JSX tag determines the type of the React element.

    Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX <Foo /> expression, Foo must be in scope.

    hashtag
    React Must Be in Scope

    Since JSX compiles into calls to React.createElement, the React library must also always be in scope from your JSX code.

    For example, both of the imports are necessary in this code, even though React and CustomButton are not directly referenced from JavaScript:

    If you don't use a JavaScript bundler and loaded React from a <script> tag, it is already in scope as the React global.

    hashtag
    Using Dot Notation for JSX Type

    You can also refer to a React component using dot-notation from within JSX. This is convenient if you have a single module that exports many React components. For example, if MyComponents.DatePicker is a component, you can use it directly from JSX with:

    hashtag
    User-Defined Components Must Be Capitalized

    When an element type starts with a lowercase letter, it refers to a built-in component like <div> or <span> and results in a string 'div' or 'span' passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.

    We recommend naming components with a capital letter. If you do have a component that starts with a lowercase letter, assign it to a capitalized variable before using it in JSX.

    For example, this code will not run as expected:

    To fix this, we will rename hello to Hello and use <Hello /> when referring to it:

    hashtag
    Choosing the Type at Runtime

    You cannot use a general expression as the React element type. If you do want to use a general expression to indicate the type of the element, just assign it to a capitalized variable first. This often comes up when you want to render a different component based on a prop:

    To fix this, we will assign the type to a capitalized variable first:

    hashtag
    Props in JSX

    There are several different ways to specify props in JSX.

    hashtag
    JavaScript Expressions as Props

    You can pass any JavaScript expression as a prop, by surrounding it with {}. For example, in this JSX:

    For MyComponent, the value of props.foo will be 10 because the expression 1 + 2 + 3 + 4 gets evaluated.

    if statements and for loops are not expressions in JavaScript, so they can't be used in JSX directly. Instead, you can put these in the surrounding code. For example:

    You can learn more about conditional rendering and loops in the corresponding sections.

    hashtag
    String Literals

    You can pass a string literal as a prop. These two JSX expressions are equivalent:

    When you pass a string literal, its value is HTML-unescaped. So these two JSX expressions are equivalent:

    This behavior is usually not relevant. It's only mentioned here for completeness.

    hashtag
    Props Default to "True"

    If you pass no value for a prop, it defaults to true. These two JSX expressions are equivalent:

    In general, we don't recommend not passing a value for a prop, because it can be confused with the ES6 object shorthandarrow-up-right {foo} which is short for {foo: foo} rather than {foo: true}. This behavior is just there so that it matches the behavior of HTML.

    hashtag
    Spread Attributes

    If you already have props as an object, and you want to pass it in JSX, you can use ... as a "spread" syntax to pass the whole props object. These two components are equivalent:

    You can also pick specific props that your component will consume while passing all other props using the spread syntax.

    In the example above, the kind prop is safely consumed and is not passed on to the <button> element in the DOM. All other props are passed via the ...other object making this component really flexible. You can see that it passes an onClick and children props.

    Spread attributes can be useful but they also make it easy to pass unnecessary props to components that don't care about them or to pass invalid HTML attributes to the DOM. We recommend using this syntax sparingly.

    hashtag
    Children in JSX

    In JSX expressions that contain both an opening tag and a closing tag, the content between those tags is passed as a special prop: props.children. There are several different ways to pass children:

    hashtag
    String Literals

    You can put a string between the opening and closing tags and props.children will just be that string. This is useful for many of the built-in HTML elements. For example:

    This is valid JSX, and props.children in MyComponent will simply be the string "Hello world!". HTML is unescaped, so you can generally write JSX just like you would write HTML in this way:

    JSX removes whitespace at the beginning and ending of a line. It also removes blank lines. New lines adjacent to tags are removed; new lines that occur in the middle of string literals are condensed into a single space. So these all render to the same thing:

    hashtag
    JSX Children

    You can provide more JSX elements as the children. This is useful for displaying nested components:

    You can mix together different types of children, so you can use string literals together with JSX children. This is another way in which JSX is like HTML, so that this is both valid JSX and valid HTML:

    A React component can also return an array of elements:

    hashtag
    JavaScript Expressions as Children

    You can pass any JavaScript expression as children, by enclosing it within {}. For example, these expressions are equivalent:

    This is often useful for rendering a list of JSX expressions of arbitrary length. For example, this renders an HTML list:

    JavaScript expressions can be mixed with other types of children. This is often useful in lieu of string templates:

    hashtag
    Functions as Children

    Normally, JavaScript expressions inserted in JSX will evaluate to a string, a React element, or a list of those things. However, props.children works just like any other prop in that it can pass any sort of data, not just the sorts that React knows how to render. For example, if you have a custom component, you could have it take a callback as props.children:

    Children passed to a custom component can be anything, as long as that component transforms them into something React can understand before rendering. This usage is not common, but it works if you want to stretch what JSX is capable of.

    hashtag
    Booleans, Null, and Undefined Are Ignored

    false, null, undefined, and true are valid children. They simply don't render. These JSX expressions will all render to the same thing:

    This can be useful to conditionally render React elements. This JSX renders the <Header /> component only if showHeader is true:

    One caveat is that some "falsy" valuesarrow-up-right, such as the 0 number, are still rendered by React. For example, this code will not behave as you might expect because 0 will be printed when props.messages is an empty array:

    To fix this, make sure that the expression before && is always boolean:

    Conversely, if you want a value like false, true, null, or undefined to appear in the output, you have to convert it to a stringarrow-up-right first:

    the online Babel compilerarrow-up-right
    ReactDOM.createRoot(rootNode).render(<App />);
    <Suspense fallback={<h1>Loading...</h1>}>
      <ProfilePhoto />
      <ProfileDetails />
    </Suspense>
    <SuspenseList revealOrder="forwards">
      <Suspense fallback={"Loading..."}>
        <ProfilePicture id={1} />
      </Suspense>
      <Suspense fallback={"Loading..."}>
        <ProfilePicture id={2} />
      </Suspense>
      <Suspense fallback={"Loading..."}>
        <ProfilePicture id={3} />
      </Suspense>
      ...
    </SuspenseList>
    const SUSPENSE_CONFIG = { timeoutMs: 2000 };
    
    const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
    const SUSPENSE_CONFIG = { timeoutMs: 2000 };
    
    function App() {
      const [resource, setResource] = useState(initialResource);
      const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
      return (
        <>
          <button
            disabled={isPending}
            onClick={() => {
              startTransition(() => {
                const nextUserId = getNextId(resource.userId);
                setResource(fetchProfileData(nextUserId));
              });
            }}
          >
            Next
          </button>
          {isPending ? " Loading..." : null}
          <Suspense fallback={<Spinner />}>
            <ProfilePage resource={resource} />
          </Suspense>
        </>
      );
    }
    const SUSPENSE_CONFIG = { timeoutMs: 2000 };
    const deferredValue = useDeferredValue(value, { timeoutMs: 2000 });
    function App() {
      const [text, setText] = useState("hello");
      const deferredText = useDeferredValue(text, { timeoutMs: 2000 });
    
      return (
        <div className="App">
          {/* Keep passing the current text to the input */}
          <input value={text} onChange={handleChange} />
          ...
          {/* But the list is allowed to "lag behind" when necessary */}
          <MySlowList text={deferredText} />
        </div>
      );
    }
    const SUSPENSE_CONFIG = { timeoutMs: 2000 };
    class Columns extends React.Component {
      render() {
        return (
          <div>
            <td>Hello</td>
            <td>World</td>
          </div>
        );
      }
    }
    <table>
      <tr>
        <div>
          <td>Hello</td>
          <td>World</td>
        </div>
      </tr>
    </table>
    class Columns extends React.Component {
      render() {
        return (
          <React.Fragment>
            <td>Hello</td>
            <td>World</td>
          </React.Fragment>
        );
      }
    }
    <table>
      <tr>
        <td>Hello</td>
        <td>World</td>
      </tr>
    </table>
    class Columns extends React.Component {
      render() {
        return (
          <>
            <td>Hello</td>
            <td>World</td>
          </>
        );
      }
    }
    function Glossary(props) {
      return (
        <dl>
          {props.items.map((item) => (
            // Without the `key`, React will fire a key warning
            <React.Fragment key={item.id}>
              <dt>{item.term}</dt>
              <dd>{item.description}</dd>
            </React.Fragment>
          ))}
        </dl>
      );
    }
    <div class="top-[117px]">
      <!-- ... -->
    </div>
    <div class="top-[117px] lg:top-[344px]">
      <!-- ... -->
    </div>
    <div class="bg-[#bada55] text-[22px] before:content-['Festivus']">
      <!-- ... -->
    </div>
    <div class="[mask-type:luminance]">
      <!-- ... -->
    </div>
    <div class="[mask-type:luminance] hover:[mask-type:alpha]">
      <!-- ... -->
    </div>
    <div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]">
      <!-- ... -->
    </div>
    <div class="grid grid-cols-[1fr_500px_2fr]">
      <!-- ... -->
    </div>
    <div class="bg-[url('/what_a_rush.png')]">
      <!-- ... -->
    </div>
    <div class="before:content-['hello\_world']">
      <!-- ... -->
    </div>
    <!-- Will generate a font-size utility -->
    <div class="text-[22px]">...</div>
    
    <!-- Will generate a color utility -->
    <div class="text-[#bada55]">...</div>
    <div class="text-[var(--my-var)]">...</div>
    <!-- Will generate a font-size utility -->
    <div class="text-[length:var(--my-var)]">...</div>
    
    <!-- Will generate a color utility -->
    <div class="text-[color:var(--my-var)]">...</div>
    .btn {
      background: blue;
      /* ... */
    }
    
    .bg-black {
      background: black;
    }
    <button class="btn bg-black">...</button>
    <button class="bg-black btn">...</button>
    <!doctype html>
    <html lang="en" class="text-gray-900 bg-gray-100 font-serif">
      <!-- ... -->
    </html>
    <!-- Will look like a card, but with square corners -->
    <div class="card rounded-none">
      <!-- ... -->
    </div>
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    const element = <div />;
    const element = <Welcome name="Sara" />;
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="Sara" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    function App() {
      return (
        <div>
          <Welcome name="Sara" />
          <Welcome name="Cahal" />
          <Welcome name="Edite" />
        </div>
      );
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );
    function Comment(props) {
      return (
        <div className="Comment">
          <div className="UserInfo">
            <img
              className="Avatar"
              src={props.author.avatarUrl}
              alt={props.author.name}
            />
            <div className="UserInfo-name">{props.author.name}</div>
          </div>
          <div className="Comment-text">{props.text}</div>
          <div className="Comment-date">{formatDate(props.date)}</div>
        </div>
      );
    }
    function Avatar(props) {
      return (
        <img className="Avatar"
          src={props.user.avatarUrl}
          alt={props.user.name}
        />
      );
    }
    function Comment(props) {
      return (
        <div className="Comment">
          <div className="UserInfo">
            <Avatar user={props.author} />
            <div className="UserInfo-name">
              {props.author.name}
            </div>
          </div>
          <div className="Comment-text">
            {props.text}
          </div>
          <div className="Comment-date">
            {formatDate(props.date)}
          </div>
        </div>
      );
    }
    function UserInfo(props) {
      return (
        <div className="UserInfo">
          <Avatar user={props.user} />
          <div className="UserInfo-name">
            {props.user.name}
          </div>
        </div>
      );
    }
    function Comment(props) {
      return (
        <div className="Comment">
          <UserInfo user={props.author} />
          <div className="Comment-text">
            {props.text}
          </div>
          <div className="Comment-date">
            {formatDate(props.date)}
          </div>
        </div>
      );
    }
    function sum(a, b) {
      return a + b;
    }
    function withdraw(account, amount) {
      account.total -= amount;
    }
    import ReactCSSTransitionGroup from "react-transition-group"; // ES6
    var ReactCSSTransitionGroup = require("react-transition-group"); // ES5 with npm
    class TodoList extends React.Component {
      constructor(props) {
        super(props);
        this.state = {items: ['hello', 'world', 'click', 'me']};
        this.handleAdd = this.handleAdd.bind(this);
      }
    
      handleAdd() {
        const newItems = this.state.items.concat([
          prompt('Enter some text')
        ]);
        this.setState({items: newItems});
      }
    
      handleRemove(i) {
        let newItems = this.state.items.slice();
        newItems.splice(i, 1);
        this.setState({items: newItems});
      }
    
      render() {
        const items = this.state.items.map((item, i) => (
          <div key={i} onClick={() => this.handleRemove(i)}>
            {item}
          </div>
        ));
    
        return (
          <div>
            <button onClick={this.handleAdd}>Add Item</button>
            <ReactCSSTransitionGroup
              transitionName="example"
              transitionEnterTimeout={500}
              transitionLeaveTimeout={300}>
              {items}
            </ReactCSSTransitionGroup>
          </div>
        );
      }
    }
    .example-enter {
      opacity: 0.01;
    }
    
    .example-enter.example-enter-active {
      opacity: 1;
      transition: opacity 500ms ease-in;
    }
    
    .example-leave {
      opacity: 1;
    }
    
    .example-leave.example-leave-active {
      opacity: 0.01;
      transition: opacity 300ms ease-in;
    }
    render() {
      return (
        <ReactCSSTransitionGroup
          transitionName="example"
          transitionAppear={true}
          transitionAppearTimeout={500}
          transitionEnter={false}
          transitionLeave={false}>
          <h1>Fading at Initial Mount</h1>
        </ReactCSSTransitionGroup>
      );
    }
    .example-appear {
      opacity: 0.01;
    }
    
    .example-appear.example-appear-active {
      opacity: 1;
      transition: opacity 0.5s ease-in;
    }
    // ...
    <ReactCSSTransitionGroup
      transitionName={ {
        enter: 'enter',
        enterActive: 'enterActive',
        leave: 'leave',
        leaveActive: 'leaveActive',
        appear: 'appear',
        appearActive: 'appearActive'
      } }>
      {item}
    </ReactCSSTransitionGroup>
    
    <ReactCSSTransitionGroup
      transitionName={ {
        enter: 'enter',
        leave: 'leave',
        appear: 'appear'
      } }>
      {item2}
    </ReactCSSTransitionGroup>
    // ...
    render() {
      const items = this.state.items.map((item, i) => (
        <div key={item} onClick={() => this.handleRemove(i)}>
          <ReactCSSTransitionGroup transitionName="example">
            {item}
          </ReactCSSTransitionGroup>
        </div>
      ));
    
      return (
        <div>
          <button onClick={this.handleAdd}>Add Item</button>
          {items}
        </div>
      );
    }
    import ReactCSSTransitionGroup from 'react-transition-group';
    
    function ImageCarousel(props) {
      return (
        <div>
          <ReactCSSTransitionGroup
            transitionName="carousel"
            transitionEnterTimeout={300}
            transitionLeaveTimeout={300}>
            <img src={props.imageSrc} key={props.imageSrc} />
          </ReactCSSTransitionGroup>
        </div>
      );
    }
    import ReactTransitionGroup from "react-addons-transition-group"; // ES6
    var ReactTransitionGroup = require("react-addons-transition-group"); // ES5 with npm
    <ReactTransitionGroup component="ul">
      {/* ... */}
    </ReactTransitionGroup>
    <ReactTransitionGroup component="ul" className="animated-list">
      {/* ... */}
    </ReactTransitionGroup>
    function FirstChild(props) {
      const childrenArray = React.Children.toArray(props.children);
      return childrenArray[0] || null;
    }
    <ReactTransitionGroup component={FirstChild}>
      {someCondition ? <MyComponent /> : null}
    </ReactTransitionGroup>
    componentWillAppear(callback);
    componentDidAppear();
    componentWillEnter(callback);
    componentDidEnter();
    componentWillLeave(callback);
    componentDidLeave();
    const numbers = [1, 2, 3, 4, 5];
    const doubled = numbers.map((number) => number * 2);
    console.log(doubled);
    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      <li>{number}</li>
    );
    ReactDOM.render(
      <ul>{listItems}</ul>,
      document.getElementById('root')
    );
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li>{number}</li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li key={number.toString()}>
          {number}
        </li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );
    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      <li key={number.toString()}>
        {number}
      </li>
    );
    const todoItems = todos.map((todo) =>
      <li key={todo.id}>
        {todo.text}
      </li>
    );
    const todoItems = todos.map((todo, index) =>
      // Only do this if items have no stable IDs
      <li key={index}>
        {todo.text}
      </li>
    );
    function ListItem(props) {
      const value = props.value;
      return (
        // Wrong! There is no need to specify the key here:
        <li key={value.toString()}>
          {value}
        </li>
      );
    }
    
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        // Wrong! The key should have been specified here:
        <ListItem value={number} />
      );
      return (
        <ul>
          {listItems}
        </ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );
    function ListItem(props) {
      // Correct! There is no need to specify the key here:
      return <li>{props.value}</li>;
    }
    
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        // Correct! Key should be specified inside the array.
        <ListItem key={number.toString()} value={number} />
      );
      return (
        <ul>
          {listItems}
        </ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );
    function Blog(props) {
      const sidebar = (
        <ul>
          {props.posts.map((post) =>
            <li key={post.id}>
              {post.title}
            </li>
          )}
        </ul>
      );
      const content = props.posts.map((post) =>
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.content}</p>
        </div>
      );
      return (
        <div>
          {sidebar}
          <hr />
          {content}
        </div>
      );
    }
    
    const posts = [
      {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
      {id: 2, title: 'Installation', content: 'You can install React from npm.'}
    ];
    ReactDOM.render(
      <Blog posts={posts} />,
      document.getElementById('root')
    );
    const content = posts.map((post) =>
      <Post
        key={post.id}
        id={post.id}
        title={post.title} />
    );
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      );
      return (
        <ul>
          {listItems}
        </ul>
      );
    }
    function NumberList(props) {
      const numbers = props.numbers;
      return (
        <ul>
          {numbers.map((number) =>
            <ListItem key={number.toString()}
                      value={number} />
          )}
        </ul>
      );
    }
    <MyButton color="blue" shadowSize={2}>
      Click Me
    </MyButton>
    React.createElement(MyButton, { color: "blue", shadowSize: 2 }, "Click Me");
    <div className="sidebar" />
    React.createElement("div", { className: "sidebar" });
    import React from 'react';
    import CustomButton from './CustomButton';
    
    function WarningButton() {
      // return React.createElement(CustomButton, {color: 'red'}, null);
      return <CustomButton color="red" />;
    }
    import React from 'react';
    
    const MyComponents = {
      DatePicker: function DatePicker(props) {
        return <div>Imagine a {props.color} datepicker here.</div>;
      }
    }
    
    function BlueDatePicker() {
      return <MyComponents.DatePicker color="blue" />;
    }
    import React from 'react';
    
    // Wrong! This is a component and should have been capitalized:
    function hello(props) {
      // Correct! This use of <div> is legitimate because div is a valid HTML tag:
      return <div>Hello {props.toWhat}</div>;
    }
    
    function HelloWorld() {
      // Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
      return <hello toWhat="World" />;
    }
    import React from 'react';
    
    // Correct! This is a component and should be capitalized:
    function Hello(props) {
      // Correct! This use of <div> is legitimate because div is a valid HTML tag:
      return <div>Hello {props.toWhat}</div>;
    }
    
    function HelloWorld() {
      // Correct! React knows <Hello /> is a component because it's capitalized.
      return <Hello toWhat="World" />;
    }
    import React from 'react';
    import { PhotoStory, VideoStory } from './stories';
    
    const components = {
      photo: PhotoStory,
      video: VideoStory
    };
    
    function Story(props) {
      // Wrong! JSX type can't be an expression.
      return <components[props.storyType] story={props.story} />;
    }
    import React from 'react';
    import { PhotoStory, VideoStory } from './stories';
    
    const components = {
      photo: PhotoStory,
      video: VideoStory
    };
    
    function Story(props) {
      // Correct! JSX type can be a capitalized variable.
      const SpecificStory = components[props.storyType];
      return <SpecificStory story={props.story} />;
    }
    <MyComponent foo={1 + 2 + 3 + 4} />
    function NumberDescriber(props) {
      let description;
      if (props.number % 2 == 0) {
        description = <strong>even</strong>;
      } else {
        description = <i>odd</i>;
      }
      return <div>{props.number} is an {description} number</div>;
    }
    <MyComponent message="hello world" />
    
    <MyComponent message={'hello world'} />
    <MyComponent message="&lt;3" />
    
    <MyComponent message={'<3'} />
    <MyTextBox autocomplete />
    
    <MyTextBox autocomplete={true} />
    function App1() {
      return <Greeting firstName="Ben" lastName="Hector" />;
    }
    
    function App2() {
      const props = {firstName: 'Ben', lastName: 'Hector'};
      return <Greeting {...props} />;
    }
    const Button = props => {
      const { kind, ...other } = props;
      const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
      return <button className={className} {...other} />;
    };
    
    const App = () => {
      return (
        <div>
          <Button kind="primary" onClick={() => console.log("clicked!")}>
            Hello World!
          </Button>
        </div>
      );
    };
    <MyComponent>Hello world!</MyComponent>
    <div>This is valid HTML &amp; JSX at the same time.</div>
    <div>Hello World</div>
    
    <div>
      Hello World
    </div>
    
    <div>
      Hello
      World
    </div>
    
    <div>
    
      Hello World
    </div>
    <MyContainer>
      <MyFirstComponent />
      <MySecondComponent />
    </MyContainer>
    <div>
      Here is a list:
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
      </ul>
    </div>
    render() {
      // No need to wrap list items in an extra element!
      return [
        // Don't forget the keys :)
        <li key="A">First item</li>,
        <li key="B">Second item</li>,
        <li key="C">Third item</li>,
      ];
    }
    <MyComponent>foo</MyComponent>
    
    <MyComponent>{'foo'}</MyComponent>
    function Item(props) {
      return <li>{props.message}</li>;
    }
    
    function TodoList() {
      const todos = ['finish doc', 'submit pr', 'nag dan to review'];
      return (
        <ul>
          {todos.map((message) => <Item key={message} message={message} />)}
        </ul>
      );
    }
    function Hello(props) {
      return <div>Hello {props.addressee}!</div>;
    }
    // Calls the children callback numTimes to produce a repeated component
    function Repeat(props) {
      let items = [];
      for (let i = 0; i < props.numTimes; i++) {
        items.push(props.children(i));
      }
      return <div>{items}</div>;
    }
    
    function ListOfTenThings() {
      return (
        <Repeat numTimes={10}>
          {(index) => <div key={index}>This is item {index} in the list</div>}
        </Repeat>
      );
    }
    <div />
    
    <div></div>
    
    <div>{false}</div>
    
    <div>{null}</div>
    
    <div>{undefined}</div>
    
    <div>{true}</div>
    <div>
      {showHeader && <Header />}
      <Content />
    </div>
    <div>
      {props.messages.length &&
        <MessageList messages={props.messages} />
      }
    </div>
    <div>
      {props.messages.length > 0 &&
        <MessageList messages={props.messages} />
      }
    </div>
    <div>
      My JavaScript variable is {String(myVariable)}.
    </div>
    FED Conversion: Search

    DNT-981arrow-up-right

    Sitecore Template: Search

    DNT-826Component Conversion: FormRecaptcha

    DNT-909arrow-up-right

    FED Conversion: FormRecaptcha

    DNT-821Component Conversion: Jurisdiction Selector

    DNT-965arrow-up-right

    Sitecore Template: Jurisdiction Selector

    DNT-845Hero - option for smaller image(new component)

    DNT-888arrow-up-right

    Test Subtask

    DNT-941arrow-up-right

    Sitecore Template: Hero - option for smaller image

    DNT-839Component Conversion: DessaTemp

    DNT-896arrow-up-right

    FED Conversion: DessaTemp

    DNT-947arrow-up-right

    Sitecore Template: DessaTemp

    DNT-2935arrow-up-right

    Google Tag Analytics: Cross Domain - Old URL is retreived and overrides the link to the needed URL, which prevents cross-domain tags from working.

    Execute test cases

    DNT-2776Search: Business/Residential Toggle needs to be translated

    DNT-2788arrow-up-right

    Sitecore

    DNT-2916arrow-up-right

    Feature: Is duke My Service Provider?

    Is Duke My Service Provider?

    DNT-2654arrow-up-right

    a11y audit - Form

    JSS Accessibility Audit

    DNT-2569arrow-up-right

    Add updated classes to Rich Text Editor in Sitecore CMS

    JSS Application Component Conversion

    DNT-2622arrow-up-right

    Finalize list of classes and elements needed

    DNT-2209arrow-up-right

    UX Review of JSS Test

    Open Prod Issues / Hypercare

    DNT-2348arrow-up-right

    Tabs & Back Button

    DNT-2792arrow-up-right

    Node Server Errors

    Open Prod Issues / Hypercare

    DNT-2869arrow-up-right

    Create new component for Push-down Panels (should be structured like accordions)

    JSS Application Component Conversion

    DNT-2873arrow-up-right

    Component - Segmented Control

    JSS Application Component Conversion

    DNT-2649arrow-up-right

    a11y audit - Modal

    JSS Accessibility Audit

    DNT-2842Electric Vehicle Calculator

    DNT-2912arrow-up-right

    Develop test cases

    DNT-2811a11y audit - MoreInfo

    DNT-2839arrow-up-right

    Develop test cases

    DNT-792arrow-up-right

    JSS Starter Component Conversion

    DNT-390arrow-up-right

    Open Prod Issues / Hypercare

    DNT-2772arrow-up-right

    Consolidate "Video" components

    JSS Code Base Maintenance

    DNT-2889arrow-up-right

    Strange behavior with Back button

    Open Prod Issues / Hypercare

    DNT-2895arrow-up-right

    SmartSaver Rebate Table: App Component Redesign

    JSS Application Component Conversion

    DNT-2878arrow-up-right

    Find it Duke Updates

    Post-R8 Approved (Non-Optimization)

    DNT-2901arrow-up-right

    Update legacy FID form

    DNT-2910arrow-up-right

    Develop test cases

    DNT-2870arrow-up-right

    Add zipCode to VPV for find it duke contractor search

    Open Prod Issues / Hypercare

    DNT-2930arrow-up-right

    Hero Component Conversion

    JSS Application Component Conversion

    DNT-2937arrow-up-right

    Data Attribution for IDMSP?

    Is Duke My Service Provider?

    Missing rich text button style

    DNT-2925arrow-up-right

    JSS Conversion: Push-down Panel component

    Open Prod Issues / Hypercare

    DNT-2208arrow-up-right

    Analytics Review of JSS Test

    Open Prod Issues / Hypercare

    DNT-2681arrow-up-right
    DNT-2710arrow-up-right
    DNT-2712arrow-up-right
    DNT-2834arrow-up-right
    DNT-2836arrow-up-right
    DNT-2229arrow-up-right
    DNT-2920arrow-up-right
    DNT-2893arrow-up-right
    DNT-2907arrow-up-right
    DNT-2915arrow-up-right
    DNT-2888arrow-up-right
    DNT-2685arrow-up-right
    DNT-2623arrow-up-right
    DNT-2624arrow-up-right
    DNT-2625arrow-up-right
    DNT-2293arrow-up-right
    DNT-1828arrow-up-right
    DNT-1829arrow-up-right
    DNT-2432arrow-up-right
    DNT-2931arrow-up-right
    DNT-2936arrow-up-right
    DNT-2651arrow-up-right
    DNT-2673arrow-up-right
    DNT-2684arrow-up-right
    DNT-2840arrow-up-right
    DNT-2390arrow-up-right
    DNT-2397arrow-up-right
    DNT-2403arrow-up-right
    DNT-2404arrow-up-right
    DNT-2405arrow-up-right
    DNT-2407arrow-up-right
    DNT-2408arrow-up-right
    DNT-2409arrow-up-right
    DNT-2411arrow-up-right
    DNT-2412arrow-up-right
    DNT-2413arrow-up-right
    DNT-2415arrow-up-right
    DNT-2422arrow-up-right
    DNT-2450arrow-up-right
    DNT-2452arrow-up-right
    DNT-2465arrow-up-right
    DNT-1272arrow-up-right
    DNT-1279arrow-up-right
    DNT-1273arrow-up-right
    DNT-1274arrow-up-right
    DNT-1275arrow-up-right
    DNT-1190arrow-up-right
    DNT-1191arrow-up-right
    DNT-1192arrow-up-right
    DNT-1193arrow-up-right
    DNT-1554arrow-up-right
    DNT-661arrow-up-right
    DNT-2626arrow-up-right
    DNT-452arrow-up-right
    DNT-454arrow-up-right
    DNT-453arrow-up-right
    DNT-456arrow-up-right
    DNT-455arrow-up-right
    DNT-1593arrow-up-right
    DNT-1454arrow-up-right
    DNT-751arrow-up-right
    DNT-544arrow-up-right
    DNT-1301arrow-up-right
    DNT-1663arrow-up-right
    DNT-1827arrow-up-right
    DNT-1451arrow-up-right
    DNT-1830arrow-up-right
    DNT-1861arrow-up-right
    DNT-1863arrow-up-right
    DNT-1865arrow-up-right
    DNT-1879arrow-up-right
    DNT-2049arrow-up-right
    DNT-2294arrow-up-right
    DNT-2370arrow-up-right
    DNT-2441arrow-up-right
    DNT-2460arrow-up-right
    DNT-2562arrow-up-right
    DNT-2800arrow-up-right
    DNT-2423arrow-up-right
    DNT-2570arrow-up-right
    DNT-2885arrow-up-right
    DNT-2933arrow-up-right
    DNT-2900arrow-up-right
    DNT-2917arrow-up-right
    DNT-2927arrow-up-right
    DNT-2934arrow-up-right
    DNT-2938arrow-up-right
    DNT-2939arrow-up-right
    DNT-2940arrow-up-right
    DNT-985arrow-up-right
    DNT-930arrow-up-right
    DNT-2682arrow-up-right
    DNT-2778arrow-up-right
    DNT-2655arrow-up-right
    DNT-2380arrow-up-right
    DNT-2509arrow-up-right
    DNT-1297arrow-up-right
    Issue Type: Epic
    Priority: Critical
    Priority: Medium
    Sets the background origin position.
  • Sets position of a background image.

  • Sets repetition of a background image.

  • Sets background size of a background image.

  • Sets the background color gradients and where to stop.

  • Skews an element that has transform applied.
  • Sets the origin of an element's transforms. Think of the origin as pushing a thumbtack into the element at the specified position.

  • Sets the transform of an element.

  • Sets how an element's background images blend with its background color.
    Controls how flex items grow and shrink.
  • Controls how flex items grow.

  • Controls how flex items shrink.

  • Controls how flex items are ordered.

  • Sets the height of an element.
  • Sets the minimum height of an element.

  • Sets the maxiumum height of an element.

  • Sets border color between child elements when using divide width.
  • Sets border opacity between elements when used with divide-[color].

  • Sets border style between elements when using divide width.

  • Sets the width of outline rings using box shadows.

  • Sets the color of the outline ring.

  • Sets the opacity of the outline ring.

  • Sets an offset for outline rings.

  • Sets the color of the outline ring offset.

  • Sets the delay for transitions.

    Specifies whether an element is the target of mouse events.
  • Sets whether an element is resizable, along with direction.

  • Controls whether the user can select text.

  • Controls whether an element is visually hidden but still accessible to screen readers.

  • Sets a grid item size and location within the grid row.
  • Controls the auto placement of grid elements.

  • Controls the size of auto-generated (implicit) grid columns.

  • Controls the size of auto-generated (implicit) grid rows.

  • Sets the gaps (gutters) between rows and columns.

  • .justify-between

    justify-content: space-between;

    .justify-around

    justify-content: space-around;

    .justify-evenly

    justify-content: space-evenly;

  • Controls default alignment for items on the inline axis for grids.

  • Controls element alignment on the inline axis for a grid item.

  • Controls how lines are positioned in multi-line flex containers.

    .content-start
    align-content: flex-start;

    .content-center

    align-content: center;

    .content-end

    align-content: flex-end;

  • Sets flex items position along a contrainer's cross axis.

  • Controls how an individual flex item is positioned along container's cross axis.

  • Controls alignment in both directions at once for grid or flexbox.

    .place-content-center
    place-content: center;

    .place-content-start

    place-content: start;

    .place-content-end

    place-content: end;

  • Controls alignment of items in both directions at once for grid or flexbox.

  • Controls alignment of individual element in both directions at once for grid or flexbox.

  • .text-gray-100

    --tw-text-opacity: 1; color: rgba(243, 244, 246, var(--tw-text-opacity));

    .text-gray-200

    --tw-text-opacity: 1; color: rgba(229, 231, 235, var(--tw-text-opacity));

    .text-gray-300

    --tw-text-opacity: 1; color: rgba(209, 213, 219, var(--tw-text-opacity));

    .text-gray-400

    --tw-text-opacity: 1; color: rgba(156, 163, 175, var(--tw-text-opacity));

    .text-gray-500

    --tw-text-opacity: 1; color: rgba(107, 114, 128, var(--tw-text-opacity));

    .text-gray-600

    --tw-text-opacity: 1; color: rgba(75, 85, 99, var(--tw-text-opacity));

    .text-gray-700

    --tw-text-opacity: 1; color: rgba(55, 65, 81, var(--tw-text-opacity));

    .text-gray-800

    --tw-text-opacity: 1; color: rgba(31, 41, 55, var(--tw-text-opacity));

    .text-gray-900

    --tw-text-opacity: 1; color: rgba(17, 24, 39, var(--tw-text-opacity));

    .text-red-50

    --tw-text-opacity: 1; color: rgba(254, 242, 242, var(--tw-text-opacity));

    .text-red-100

    --tw-text-opacity: 1; color: rgba(254, 226, 226, var(--tw-text-opacity));

    .text-red-200

    --tw-text-opacity: 1; color: rgba(254, 202, 202, var(--tw-text-opacity));

    .text-red-300

    --tw-text-opacity: 1; color: rgba(252, 165, 165, var(--tw-text-opacity));

    .text-red-400

    --tw-text-opacity: 1; color: rgba(248, 113, 113, var(--tw-text-opacity));

    .text-red-500

    --tw-text-opacity: 1; color: rgba(239, 68, 68, var(--tw-text-opacity));

    .text-red-600

    --tw-text-opacity: 1; color: rgba(220, 38, 38, var(--tw-text-opacity));

    .text-red-700

    --tw-text-opacity: 1; color: rgba(185, 28, 28, var(--tw-text-opacity));

    .text-red-800

    --tw-text-opacity: 1; color: rgba(153, 27, 27, var(--tw-text-opacity));

    .text-red-900

    --tw-text-opacity: 1; color: rgba(127, 29, 29, var(--tw-text-opacity));

    .text-yellow-50

    --tw-text-opacity: 1; color: rgba(255, 251, 235, var(--tw-text-opacity));

    .text-yellow-100

    --tw-text-opacity: 1; color: rgba(254, 243, 199, var(--tw-text-opacity));

    .text-yellow-200

    --tw-text-opacity: 1; color: rgba(253, 230, 138, var(--tw-text-opacity));

    .text-yellow-300

    --tw-text-opacity: 1; color: rgba(252, 211, 77, var(--tw-text-opacity));

    .text-yellow-400

    --tw-text-opacity: 1; color: rgba(251, 191, 36, var(--tw-text-opacity));

    .text-yellow-500

    --tw-text-opacity: 1; color: rgba(245, 158, 11, var(--tw-text-opacity));

    .text-yellow-600

    --tw-text-opacity: 1; color: rgba(217, 119, 6, var(--tw-text-opacity));

    .text-yellow-700

    --tw-text-opacity: 1; color: rgba(180, 83, 9, var(--tw-text-opacity));

    .text-yellow-800

    --tw-text-opacity: 1; color: rgba(146, 64, 14, var(--tw-text-opacity));

    .text-yellow-900

    --tw-text-opacity: 1; color: rgba(120, 53, 15, var(--tw-text-opacity));

    .text-green-50

    --tw-text-opacity: 1; color: rgba(236, 253, 245, var(--tw-text-opacity));

    .text-green-100

    --tw-text-opacity: 1; color: rgba(209, 250, 229, var(--tw-text-opacity));

    .text-green-200

    --tw-text-opacity: 1; color: rgba(167, 243, 208, var(--tw-text-opacity));

    .text-green-300

    --tw-text-opacity: 1; color: rgba(110, 231, 183, var(--tw-text-opacity));

    .text-green-400

    --tw-text-opacity: 1; color: rgba(52, 211, 153, var(--tw-text-opacity));

    .text-green-500

    --tw-text-opacity: 1; color: rgba(16, 185, 129, var(--tw-text-opacity));

    .text-green-600

    --tw-text-opacity: 1; color: rgba(5, 150, 105, var(--tw-text-opacity));

    .text-green-700

    --tw-text-opacity: 1; color: rgba(4, 120, 87, var(--tw-text-opacity));

    .text-green-800

    --tw-text-opacity: 1; color: rgba(6, 95, 70, var(--tw-text-opacity));

    .text-green-900

    --tw-text-opacity: 1; color: rgba(6, 78, 59, var(--tw-text-opacity));

    .text-blue-50

    --tw-text-opacity: 1; color: rgba(239, 246, 255, var(--tw-text-opacity));

    .text-blue-100

    --tw-text-opacity: 1; color: rgba(219, 234, 254, var(--tw-text-opacity));

    .text-blue-200

    --tw-text-opacity: 1; color: rgba(191, 219, 254, var(--tw-text-opacity));

    .text-blue-300

    --tw-text-opacity: 1; color: rgba(147, 197, 253, var(--tw-text-opacity));

    .text-blue-400

    --tw-text-opacity: 1; color: rgba(96, 165, 250, var(--tw-text-opacity));

    .text-blue-500

    --tw-text-opacity: 1; color: rgba(59, 130, 246, var(--tw-text-opacity));

    .text-blue-600

    --tw-text-opacity: 1; color: rgba(37, 99, 235, var(--tw-text-opacity));

    .text-blue-700

    --tw-text-opacity: 1; color: rgba(29, 78, 216, var(--tw-text-opacity));

    .text-blue-800

    --tw-text-opacity: 1; color: rgba(30, 64, 175, var(--tw-text-opacity));

    .text-blue-900

    --tw-text-opacity: 1; color: rgba(30, 58, 138, var(--tw-text-opacity));

    .text-indigo-50

    --tw-text-opacity: 1; color: rgba(238, 242, 255, var(--tw-text-opacity));

    .text-indigo-100

    --tw-text-opacity: 1; color: rgba(224, 231, 255, var(--tw-text-opacity));

    .text-indigo-200

    --tw-text-opacity: 1; color: rgba(199, 210, 254, var(--tw-text-opacity));

    .text-indigo-300

    --tw-text-opacity: 1; color: rgba(165, 180, 252, var(--tw-text-opacity));

    .text-indigo-400

    --tw-text-opacity: 1; color: rgba(129, 140, 248, var(--tw-text-opacity));

    .text-indigo-500

    --tw-text-opacity: 1; color: rgba(99, 102, 241, var(--tw-text-opacity));

    .text-indigo-600

    --tw-text-opacity: 1; color: rgba(79, 70, 229, var(--tw-text-opacity));

    .text-indigo-700

    --tw-text-opacity: 1; color: rgba(67, 56, 202, var(--tw-text-opacity));

    .text-indigo-800

    --tw-text-opacity: 1; color: rgba(55, 48, 163, var(--tw-text-opacity));

    .text-indigo-900

    --tw-text-opacity: 1; color: rgba(49, 46, 129, var(--tw-text-opacity));

    .text-purple-50

    --tw-text-opacity: 1; color: rgba(245, 243, 255, var(--tw-text-opacity));

    .text-purple-100

    --tw-text-opacity: 1; color: rgba(237, 233, 254, var(--tw-text-opacity));

    .text-purple-200

    --tw-text-opacity: 1; color: rgba(221, 214, 254, var(--tw-text-opacity));

    .text-purple-300

    --tw-text-opacity: 1; color: rgba(196, 181, 253, var(--tw-text-opacity));

    .text-purple-400

    --tw-text-opacity: 1; color: rgba(167, 139, 250, var(--tw-text-opacity));

    .text-purple-500

    --tw-text-opacity: 1; color: rgba(139, 92, 246, var(--tw-text-opacity));

    .text-purple-600

    --tw-text-opacity: 1; color: rgba(124, 58, 237, var(--tw-text-opacity));

    .text-purple-700

    --tw-text-opacity: 1; color: rgba(109, 40, 217, var(--tw-text-opacity));

    .text-purple-800

    --tw-text-opacity: 1; color: rgba(91, 33, 182, var(--tw-text-opacity));

    .text-purple-900

    --tw-text-opacity: 1; color: rgba(76, 29, 149, var(--tw-text-opacity));

    .text-pink-50

    --tw-text-opacity: 1; color: rgba(253, 242, 248, var(--tw-text-opacity));

    .text-pink-100

    --tw-text-opacity: 1; color: rgba(252, 231, 243, var(--tw-text-opacity));

    .text-pink-200

    --tw-text-opacity: 1; color: rgba(251, 207, 232, var(--tw-text-opacity));

    .text-pink-300

    --tw-text-opacity: 1; color: rgba(249, 168, 212, var(--tw-text-opacity));

    .text-pink-400

    --tw-text-opacity: 1; color: rgba(244, 114, 182, var(--tw-text-opacity));

    .text-pink-500

    --tw-text-opacity: 1; color: rgba(236, 72, 153, var(--tw-text-opacity));

    .text-pink-600

    --tw-text-opacity: 1; color: rgba(219, 39, 119, var(--tw-text-opacity));

    .text-pink-700

    --tw-text-opacity: 1; color: rgba(190, 24, 93, var(--tw-text-opacity));

    .text-pink-800

    --tw-text-opacity: 1; color: rgba(157, 23, 77, var(--tw-text-opacity));

    .text-pink-900

    --tw-text-opacity: 1; color: rgba(131, 24, 67, var(--tw-text-opacity));

  • Sets text opacity when used with text-[color].

    .text-opacity-0
    --tw-text-opacity: 0;

    .text-opacity-5

    --tw-text-opacity: 0.05;

    .text-opacity-10

    --tw-text-opacity: 0.1;

  • Sets the antialiasing of the font.

  • Sets the style of the font.

  • Sets the font number variant.

  • Sets the spacing between letters.

  • Sets the bullet style of a list.

  • Sets the position of a list's bullets.

  • Sets the placeholder color using the ::placeholder pseudo element.

  • Sets the placeholder opacity when used with placeholder-[color].

  • Sets the alignment of text.

    .text-left
    text-align: left;

    .text-center

    text-align: center;

    .text-right

    text-align: right;

  • Sets the text-decoration of an element.

    .underline
    text-decoration: underline;

    .line-through

    text-decoration: line-through;

    .no-underline

    text-decoration: none;

  • Sets the capitalization of text.

    .uppercase
    text-transform: uppercase;

    .lowercase

    text-transform: lowercase;

    .capitalize

    text-transform: capitalize;

  • Sets the overflow of text.

    .truncate
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;

    .overflow-ellipsis

    text-overflow: ellipsis;

    .overflow-clip

    text-overflow: clip;

  • Sets the vertical alignment of an inline or table-cell box.

  • Sets the whitespace of an element.

  • Sets the word breaks of an element.

  • Sets drop-shadow filter on elements (use with filter utility).
  • Sets grayscale filter on elements (use with filter utility).

  • Sets hue-rotate filter on elements (use with filter utility).

  • Sets invert filter on elements (use with filter utility).

  • Sets saturate filter on elements (use with filter utility).

  • Sets sepia filter on elements (use with filter utility).

  • Sets backdrop filter filter on elements (use with filter utility).

  • Sets backdrop blur filter on elements (use with filter utility).

  • Sets backdrop brightness filter on elements (use with filter utility).

  • Sets backdrop contrast filter on elements (use with filter utility).

  • Sets backdrop grayscale filter on elements (use with filter utility).

  • Sets backdrop hue-rotate filter on elements (use with filter utility).

  • Sets backdrop invert filter on elements (use with filter utility).

  • Sets backdrop opacity filter on elements (use with filter utility).

  • Sets backdrop saturate filter on elements (use with filter utility).

  • Sets backdrop sepia filter on elements (use with filter utility).

  • .justify-end

    justify-content: flex-end;

    .justify-center

    .text-current

    color: currentColor;

    .text-black

    --tw-text-opacity: 1; color: rgba(0, 0, 0, var(--tw-text-opacity));

    .text-white

    --tw-text-opacity: 1; color: rgba(255, 255, 255, var(--tw-text-opacity));

    .text-gray-50

    justify-content: center;

    --tw-text-opacity: 1; color: rgba(249, 250, 251, var(--tw-text-opacity));

    Apply a unique label to the <nav> containing the set of expandable menu widgets. Remove the aria-label="Site" attribute from the <nav>'s parent <div> and instead apply it to directly to the <nav>.

  • Remove the <nav> role from the container for each expandable menu.

  • Simplified, recommended markup for an expandable menu

    <div> <nav aria-label="Secondary navigation"> <ul> ...

    </ul> </nav> </div>

    Assistive technologies typically ignore the aria-label attribute when it is applied to generic elements such as a <div> or a <span>. This is noted in a separate best practices assertion.

    Resources

    • General Principles of Landmark Designarrow-up-right

    • Easy content organisation with HTML5arrow-up-right

    • Landmarks browser extensionarrow-up-right

    For "Building a Smarter Energy Future", the styling is not tied to branding such as a logo. The text is feasible to recreate using HTML and CSS. Therefore, it is recommended to use real HTML.

    /
    18.5px
    ), the contrast ratio must be at least 3:1.

    If the original presentation does not provide sufficient natural pauses, a possible approach is to provide extended audio descriptions. With this approach, playback is paused while the audio description plays, and resumes when the description has ended. An example of this approach can be found in the CaptionSync Smart Playerarrow-up-right.

    You may also keep this video as it is and provide a link to the same video with audio description. The link to the audio-described video must be adjacent to the original video and must not be difficult to find.

    Remediating this assertion will also resolve violations for the following success criterion: 1.2.3: Audio Description or Media Alternative (Prerecorded).

    <a href="https://desitecoredev92-cd.azureedge.net/_/media/pdfs/our-company/ash-management/212492-asheville-3q21-monitoring-results.pdf?la=en&rev=009ac748bbac49149c7676ac52d343f7">

    ... </svg>

    </a>

    Remediating this assertion will also solve for violations of the following success criteria:

    • 1.4.5 Images of Text

    • 2.4.4 Link Purpose

    attribute.

    <button aria-pressed="true" aria label="Parental leave - Maria"> <img src="..." alt=""> </button>

    Option 2

    Create a live region. Apply aria-live="polite", role="status", aria-atomic="true" to a visually hidden <span>. When a video is selected, update this live region to describe/summarize the change in page i.e. "Parental leave - Maria video selected".

    Simplified, recommended markup for a live region

    <span aria-live="polite" role="status">Parental leave - Maria video selected</span>

    Describing Techniquesarrow-up-right
  • Labeling Controlsarrow-up-right

  • for the image within each tab. This will ensure that the image is not announced by assistive technologies. When an image is indicated as decorative through the
    alt=""
    attribute, there's no possible benefit to doubling-up with the
    aria-hidden
    attribute. Remove the unnecessary
    aria-hidden
    attribute from the
    <img>
    .

    Recommended (simplified) markup for decorative image within a tab

    <button aria-controls="panel-site-requirements" aria-selected="false" role="tab" tabindex="-1" type="button"> <img alt=""> <span>Site Requirements</span> </button>

    Resources

    Text alternatives for imagesarrow-up-right

    Recommended (simplified) markup

    Resources

    Text alternatives for imagesarrow-up-right

    Code example using the alt attribute:

    <button>

    </button>

    Alternatively, when an image is the sole element within a functional container, an aria-label attribute can be applied to the functional container. Given the aria-label attribute provides an accessible name, the descendant image must be indicated as decorative.

    Code example using the aria-label attribute:

    </button>

    Resources

    • A link or button containing nothing but an imagearrow-up-right

    • img element with alt attributearrow-up-right

    Preferably, make the control into a <button>. If this is not possible, apply role="button" to the control. ARIA role attributes can be used to provide semantics to generic elements.

  • Apply aria-label="Feedback form" to the control. Visually hidden text within the button could also be used.

  • Apply aria-expanded attribute to the control to reflect whether the content it controls is expanded or collapsed.

  • Simplified code example

    </button>

    Remediating this assertion will also solve for violations of the following success criteria:

    • 4.1.2 Name, Role, Value

    .

    SVG simplified code

    ... </svg>

    Resources

    Contextually Marking up accessible images and SVGsarrow-up-right

    spacing following paragraphs to at least 2 times the font size;
  • letter spacing (tracking) to at least 0.12 times the font size;

  • word spacing to at least 0.16 times the font size

  • Resources

    • WCAG 2.1: Understanding Success Criterion 1.4.12: Text Spacingarrow-up-right

    • TPGi blog: Short note on getting spaced out with SC 1.4.12 Text Spacingarrow-up-right

    element,
    role="img"
    , and the
    aria-labelledby
    attribute (Simplified code)

    ... </svg>

    Resources

    Contextually Marking up accessible images and SVGsarrow-up-right

    The steps are sticky take up half of the screen, limiting screen real estate for the actual form.

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables. Ensure that the "Feedback" button does not obscure content.

    This does not mean that all content must be permanently visible. It is acceptable for content to be hidden initially if the user can make it visible through accessible controls (e.g., via a dropdown menu or disclosure widget).

    Do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using media queriesarrow-up-right for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    • Responsive designarrow-up-right

    • Understanding Success Criterion 1.4.10: Reflowarrow-up-right

    • Under-Engineered Responsive Tablesarrow-up-right

    and
    bookmarklets to your browser of choice.
  • Open the page you wish to check, then activate the DOM checking bookmarklet.

  • The W3C validatorarrow-up-right results for the page will be displayed, filter results using the Check for WCAG 2.1 parsing bookmarklet

  • Resources

    • WCAG Parsing Criterion is a PITAarrow-up-right

    • Check serialized DOM of current page (DOM Checking)arrow-up-right bookmarklet

    • W3C validatorarrow-up-right

    • bookmarklet

    Understanding Success Criterion 2.4.4: Link Purpose (In Context)arrow-up-right

  • Understanding Success Criterion 2.5.3: Label in Namearrow-up-right

  • Link Text and Appearancearrow-up-right

  • <label for="address">Address</label>

    When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use aria-describedby to associate the error message with the form field. Once the field is valid, remove the error message.

    Setting aria-describedby to point to an error message

    <label for="email">Email</label>

    Resources

    • aria-invalid (state)arrow-up-right

    • Using aria-Invalid to Indicate An Error Fieldarrow-up-right

    • Required attribute requirementsarrow-up-right

    Lack of an accessible description.
  • Neither the dialog, nor a descendant of the dialog is focused when the dialog is displayed.

  • Pressing ESCAPE on the keyboard does not dismiss the dialog.

  • Keyboard focus is not properly constrained to the modal dialog while it is active.

  • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing.

  • Recommendation

    • For dialogs like this with a visible title, use an aria-labelledby reference to the element containing the title text (the <h2>) to provide an accessible name.

    • Add role="dialog" to the containing element.

    • Ensure that the dialog or a descendant element of the dialog is focused when it is displayed, and that the trigger that caused the dialog to open receives focus when the dialog is dismissed.

    • Ensure that pressing ESCAPE on the keyboard dismisses the dialog.

    • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

    • Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply aria-hidden="true" to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the aria-hidden attribute from page content when the dialog is closed.

    Recommended markup (simplified) for dialog container

    </div>

    Resources

    • ARIA Authoring Practices Modal Dialog Examplearrow-up-right

    • Accessible Modal Dialogarrow-up-right

    • a11y-dialog widgetarrow-up-right

    Lack of the expected dialog role.
  • Neither the dialog, nor a descendant of the dialog is focused when the dialog is displayed.

  • Pressing ESCAPE on the keyboard does not dismiss the dialog.

  • Keyboard focus is not properly constrained to the modal dialog while it is active.

  • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing.

  • Recommendation

    • For dialogs like this with a visible title, use an aria-labelledby reference to the element containing the title text (the <h2>) to provide an accessible name.

    • For dialogs with a simple message in the dialog body, use aria-describedby to reference the element containing the text.

    • Add the role dialog OR alertdialog to the containing element.

    • Ensure that the dialog or a descendant element of the dialog is focused when it is displayed, and that the trigger that caused the dialog to open receives focus when the dialog is dismissed.

    • Ensure that pressing ESCAPE on the keyboard dismisses the dialog.

    • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

    • Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply aria-hidden="true" to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the aria-hidden attribute from page content when the dialog is closed.

    Resources

    • ARIA Authoring Practices Modal Dialog Examplearrow-up-right

    • Accessible Modal Dialogarrow-up-right

    • a11y-dialog widgetarrow-up-right

    attribute. Bear in mind that not all automated translation tools will translate the text in the
    aria-label
    attribute.

    Recommended markup (simplified) for "Learn More" links, with the addition of an aria-label to provide further context

    Note that the visible link phrase (in the example above "View Resources") must be present in the aria-label, otherwise this would fail SC 2.5.3 Label in Namearrow-up-right

    Resources

    • Understanding Success Criterion 2.4.4: Link Purpose (In Context)arrow-up-right

    • Understanding Success Criterion 2.5.3: Label in Namearrow-up-right

    • Link Text and Appearancearrow-up-right

    Text alternatives for imagesarrow-up-right
    Content supplied or generated by users, such as blog posts, comments, articles, images, and videos uploaded by users.

    The video is an embedded YouTube video and has several accessibility issues, including the following:

    • The Duke Energy logo is announced by screen readers as "photo image of Duke Energy".

    • There is low text contrast for white text when the video is also white or a light color.

    • The focus indicator is difficult to perceive.

    • There is no announcement for screen reader users when the volume slider is adjusted.

    • There is invalid HTML.

    • Text spacing does not work within the video.

    Recommendation

    • Remediate any issues you are able to address.

    • Tell third party content providers that they must make their content accessible.

    • Don't use providers of non-conformant third party content.

    • Use third party embedding systems that provide control over accessibility.

    • Manually or automatically check and block inaccessible third party content.

    • Provide accessible alternative formats for content that can't be remediated.

    • Tell users when third party content may not be accessible, and how they can access the content.

    Resources

    • Understanding Conformancearrow-up-right

    • Inaccessible Third-party Content and Code — Why it is Important, and How to Address itarrow-up-right

    Understanding SC 2.4.4 Link Purpose (In Context) (Level A)arrow-up-right
    <svg version="1.1" role="img" aria-labelledby="logo-title"

    <title id="logo-title">HTML5 Logo</title>

    <polygon points="107.644..." fill="#E44D26"/> ... </svg>

    Use the SVG <title> element to provide a text alternative that's appropriate for the context. To ensure support in assistive technologies, provide role="img" and aria-labelledby attributes in the <svg> element.

    The <desc> element provides an accessible description for the SVG image. It is a way of providing more detailed information about the image than the <title> element allows on its own.

    The aria-describedby attribute re-enforces the connection between the <svg> and <desc> elements. It ensures the descriptive information is available, even in browsers where accessibility support for SVG is limited.

    Resources

    Contextually Marking up accessible images and SVGsarrow-up-right

    Link Targets and 3.2.5arrow-up-right

    SVG with the aria-hidden attribute

    <button>

    ... </svg> <span>North Carolina</span> </button>

    Text alternatives for imagesarrow-up-right
    to the SVG.

    SVG with the aria-hidden attribute

    ... </svg>

    Apply aria-live="polite", aria-atomic="true", and role="status" to the container for this text.

  • When there is no search result, there is no text stating that the search netted zero results. Be sure to add text stating that there is zero results so that information is also dynamically announced.

  • If using this existing text is not possible, then a separate live region could be created elsewhere on the page - the live region can be visually hidden.

    Simplified, recommended markup for live region

    <span aria-live="polite" role="status" aria-atomic="true">39 Results Found</span>

    0

    64

    1.2.5 Audio Description (Prerecorded)

    AA

    1

    1

    0

    0

    0

    0

    1.3.1 Info and Relationships

    A

    28

    0

    3

    10

    8

    6

    1.3.2 Meaningful Sequence

    A

    5

    0

    4

    1

    0

    0

    1.3.5 Identify Input Purpose

    AA

    17

    0

    5

    12

    0

    0

    1.4.10 Reflow

    AA

    10

    1

    5

    3

    0

    1

    1.4.11 Non-text Contrast

    AA

    3

    0

    3

    0

    0

    0

    1.4.12 Text Spacing

    AA

    2

    0

    0

    2

    0

    0

    1.4.3 Contrast (Minimum)

    AA

    1

    0

    0

    0

    0

    1

    1.4.4 Resize text

    AA

    2

    0

    2

    0

    0

    0

    1.4.5 Images of Text

    AA

    2

    0

    0

    0

    0

    2

    2.1.1 Keyboard

    A

    4

    0

    0

    4

    0

    0

    2.4.3 Focus Order

    A

    17

    0

    17

    0

    0

    0

    2.4.4 Link Purpose (In Context)

    A

    3

    0

    1

    2

    0

    0

    2.4.6 Headings and Labels

    AA

    10

    1

    0

    7

    0

    2

    2.4.7 Focus Visible

    AA

    4

    0

    4

    0

    0

    0

    2.5.3 Label in Name

    A

    1

    0

    1

    0

    0

    0

    3.1.2 Language of Parts

    AA

    2

    0

    0

    0

    0

    2

    3.3.1 Error Identification

    A

    2

    0

    2

    0

    0

    0

    3.3.2 Labels or Instructions

    A

    14

    0

    9

    5

    0

    0

    3.3.3 Error Suggestion

    AA

    1

    0

    1

    0

    0

    0

    4.1.1 Parsing

    A

    2

    0

    0

    0

    2

    0

    4.1.2 Name, Role, Value

    A

    58

    8

    26

    22

    1

    1

    4.1.3 Status Messages

    AA

    2

    0

    1

    1

    0

    0

    Total Issues

    269

    11

    87

    75

    11

    79

    Not all ARIA role combinations are valid. When ARIA attributes conflict with the underlying semantics of an element, it may cause at best, have no effect or at worst, cause unpredictable behavior for assistive technologies.

    aria-label is primarily designed to provide an accessible name to an element. aria-label, aria-labelledby, aria-describedby are generally ignored by assistive technologies when they are applied to generic HTML elements that do not provide any meaningful semantic information i.e. <div>, <span>.

    • The <div> container for the set of expandable flyout menus (My Account, Billing & Payments, Products & Services, Start, Stop, & Move, Outages, Customer Service) has an aria-label="Site" attribute.

    • The <div> container for each expandable menu has an aria-label="Submenu" attribute.

    In both cases, the direct descendant of the <div> is a <nav>.

    Recommendation

    Remove the aria-label attribute from each <div> container. Instead apply the aria-label attribute to its descendant <nav>.

    Simplified, recommended markup for expandable "flyout" menu

    <div>

    </div>

    Note:

    To determine whether a role and aria-* is invalid, consult the .

    Resources

    7

    5

    1.3

    Consecutive links with the same destination

    When there are consecutive links with the same destination but have different link names, it is confusing for screen reader users and makes site navigation less efficient.

    The logo-based "Duke Energy" link and the next element, the "For Your Home" link have the same destination.

    Recommendation

    The "For Your Home" link is repetitive and does not clearly visually indicate that it is a link. It is suggested to remove link markup from "For Your Home". This will make site navigation more efficient for assistive technology and keyboard-only users.

    User testing and reviewing site analytics is recommended to determine the change is preferable to users.

    2

    4

    The <footer> has a direct descendant <footer> element. It is inappropriate to have a footer within a footer. This duplicative use of the footer landmark may cause confuse for screen reader users that make use of landmark navigation.

    Recommendation

    Do not nest <footer> or elements with role="contentinfo" within <footer>.

    Remove either the parent or child element that is conveying footer landmark semantics.

    Simplified recommended markup for footer landmark

    <footer> <!-- remove the nested footer

    <footer role="contentinfo"> ... </footer> --> <footer>

    Resources

    1.3.1 Info and Relationships A

    1

    5

    2.3

    A color contrast ratio of 3:1 is not provided for graphical objects used in controls

    In Firefox, The "@ Sign up for Email" button uses the browser's default focus indicator, which has insufficient color contrast with the footer background.

    Focus indicator: #0060DF Background: #005984 Contrast ratio: 1.4:1

    Recommendation

    Provide enough contrast between the foreground color and background color (or, more generally, "adjacent" colors that need to be distinguishable in order to correctly perceive/understand the graphical elements or their component parts) so that people who are color-blind or with moderately low vision can sufficiently distinguish important content. The contrast ratio must be at least 3:1.

    The browser's default indicator is exempt from the WCAG Non-text Contrast (Level AA) Success Criterion 1.4.11 when the adjacent colors are also the browser's default styling. The footer background is not the browser's default styling. Therefore, the Success Criterion applies to the button.

    1.4.11 Non-text Contrast AA

    1

    2

    2.4

    Decorative images are not hidden from screen reader users

    The iOS and Android badges are image-based links. For each link, the <a href> container has an aria-label attribute which provides the control's accessible name. Each link's descendant image has an alt attribute to also provide a text presentation. Given the aria-label attribute, the descendant image can be considered decorative; its alt attribute is unnecessary.

    Recommendation

    Use an empty string as text alternative alt="" for each badge link. This will ensure that the image is not announced by assistive technologies. When an image is indicated as decorative through the alt="" attribute, there's no possible benefit to doubling-up with the aria-hidden attribute. Remove the unnecessary aria-hidden attribute from the <img>.

    Recommended (simplified) markup for decorative image

    <a href="https://itunes.apple.com/us/app/duke-energy/id1325217974?mt=8" target="_blank" aria-label="Download on the App Store" >

    </a>

    Resources

    1.1.1 Non-text Content A

    2

    4

    To identify any changes in language - when certain content is in a language that's different from the overall language defined for the page - include the lang attribute on the most appropriate container/element, and set it to the value of the appropriate language code.

    When the site is set to English, the button to switch to Spanish reads "Español". The button lacks a lang attribute to specify that it is a different language than the page's primary language.

    When the page is toggled to Spanish, the test environment remains in English. The select language button's text changing to "English". The test site does not have a Spanish version.

    Note: In the production site, when the site is presented in Spanish, the <html>'s lang attribute shifts to lang="es-US" to specify the page is in (US) Spanish. The "English" button (to shift back to English version) lacks a lang="en" attribute to specify that it is a different language than the page's primary language, Spanish. Production site is out of scope for this audit.

    Recommendation

    The lang attribute must also be used to identify chunks of text in a language that is different from the document's primary language.

    When the page is in English, apply lang="es" to the button with name, "Español".

    Adding lang="..." attribute with appropriate language code

    <html lang="en"> <body> ... <button lang="es">Español</button> <button>Sign In</button> </body> </html>

    When the page is in Spanish, apply lang="en" to the button with name, "English".

    Adding lang="..." attribute with appropriate language code

    <html lang="es-US"> <body> ... <button lang="en">English</button> <button>Iniciar sesión</button> </body> </html>

    3.1.2 Language of Parts AA

    2

    4

    4.3

    The menu button does not follow the established design pattern

    A menu button is a type of button which is used to trigger a pop-up menu, but must convey this functionality to assistive technology. The menu buttons found are missing the appropriate ARIA attributes and are not identified as menu buttons.

    The "hamburger menu" button, which opens the side navigation panel, lacks the ARIA attributes to indicate that it opens a pop-up.

    Note:

    When the navigation menu button is activated, the side navigation panel opens in the behavior of a dialog. Focus remains on the button and does not move to the navigation menu. This lack of focus management is reported in a separate 2.4.3arrow-up-right assertion.

    Recommendation

    Ensure that the <button> element that triggers a pop-up menu has an aria-haspopup="true" attribute to indicate to assistive technologies that the button has an associated menu.

    Simplified, recommended markup for navigation menu button

    </button>

    Resources

    4.1.2 Name, Role, Value A

    1

    3

    4.4

    Label is not sufficiently descriptive

    Labels which do not sufficiently describe the functionality of an interactive control, such as buttons and form controls, make it difficult for users to understand the purpose of the control.

    The hamburger menu button, which opens the side navigation panel, derives its accessible name from its aria-label="menu" attribute. The name does not indicate the menu's primary purpose.

    Recommendation

    The visible text, or alternative text, used to label interactive elements must clearly and concisely describe the control's function or purpose.

    Revise the aria-label attribute to be more descriptive of the button's purpose e.g. note which menu is being opened. "Open full site navigation" is suggested.

    Simplified, recommended markup for the hamburger menu button

    </button>

    Resources

    2.4.6 Headings and Labels AA

    1

    3

    4.5

    Side navigation panel lacks the focus management required for a dialog

    The side navigation panel has the behavior of a dialog. The side navigation menu appears above a dimmed overlay covering the main page content. The interaction lacks the necessary focus management.

    When the side navigation menu opens, focus remains on the hamburger menu button and does not move to the side navigation menu.

    Focus is not constrained to the side navigation menu. A keyboard user is able to tab to focusable elements visually positioned under the dimmed overlay. As a result, it is not possible to determine what element is currently focused.

    Inversely, when the side navigation menu is closed, focus does not return to the hamburger menu button, which triggered the menu to open.

    Neither the dialog nor a descendant of the dialog programmatically receives focus when the dialog is invoked. This results in a screen reader user being unaware that the dialog has rendered on screen.

    Recommendation

    When the side navigation menu is launched, focus must be moved to the menu. The safest option for keyboard and assistive technology users is to treat the navigation menu like a dialog and use the focus management techniques required for dialogs.

    Set initial focus

    Apply role="dialog" to the <div> container for the navigation menu. Then set focus to this element with role="dialog". To achieve this, make sure that the <div> element with role="dialog" can be programmatically focused (with the JavaScript focus() method) by setting tabindex="-1" on the element.

    When the dialog is closed, return focus to the hamburger menu button which triggered the navigation menu to open.

    Focus management within the menu

    The dialog containing the navigation menu should "contain" focus. Navigating via the TAB key should cycle through the focusable controls in the dialog until the user either closes the menu (by activating the "Close" button or pressing the ESC key.

    2.4.3 Focus Order A

    1

    2

    4.6

    Side navigation does not follow established pattern for dialogs

    The side navigation panel has the behavior of a dialog, but lacks all of the necessary semantics.

    • Container for side navigation lacks role="dialog" to indicate that it is a dialog.

    • When the side navigation panel opens, focus remains on the hamburger menu button and does not move to the side navigation. As a result, screen reader users are unaware that the content has appeared.

    • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing.

    Recommendation

    • Add the role dialog to the element containing the side navigation menu and the close button.

    • For dialogs with a visible title, use an aria-labelledby reference to the element containing the title text (ideally an <h2>) to provide an accessible name. For dialogs without a visible title, use aria-label to provide an accessible name. The side navigation does not currently have an element that functions as a title. Therefore, apply aria-label

    Simplified, recommended markup for side navigation dialog

    <html> <body> <div class="dimmed-overlay" aria-hidden="true"> <main> ... </main> </div> <div role="dialog" aria-label="Navigation panel"> <nav aria-label="Main navigation" tabindex="-1"> ... </nav> </div> </body>

    Resources

    4.1.2 Name, Role, Value A

    2

    1

    4.7

    Side navigation panels lacks focus management between menu and submenu levels

    The side navigation is a horizontally sliding set of menu/submenus. There is a lack of focus management in navigation between the main menu and the submenus.

    When an element containing a submenu, such as "My Account", is selected, the content within the side navigation panel horizontally slides offscreen to display that submenu. However, focus does not move to the start of the new content. Focus remains on the control which triggered the new content, but the control is removed from the page and there is a loss of keyboard focus. As a result, screen reader users are not aware that new content has appeared.

    Recommendation

    Ensure that when a submenu is selected, focus is set to a logical target within the new content and that it triggers a meaningful announcement to assistive technology.

    From one level of navigation to the next level, there are multiple acceptable focus targets. Either set focus to the navigation link to move back up a level or to the first link within the submenu. For example, in the main navigation menu, when "My Account" is activated, set focus to either the "Main Menu" link or the "My Account" link. If the user selects to move back to the "Main Menu", set focus to the "For Your Home" heading.

    Note:

    The site's side navigation panel does not use the ARIA menu pattern. This assertion uses the terms "menu" and "submenu" for the sake of simplicity. It is a good decision to not use the ARIA menu pattern and the use of these terms should not be interpreted as encouraging use of the ARIA menu pattern in this context. The menu pattern is intended for desktop-like applications and is inappropriate for a site's navigation.

    2.4.3 Focus Order A

    1

    2

    4.8

    ARIA attribute applied to generic element

    Not all ARIA-role combinations are valid. When ARIA attributes conflict with the underlying semantics of an element, it may cause at best, have no effect or at worst, cause unpredictable behavior for assistive technologies.

    aria-label is primarily designed to provide an accessible name to an interactive element or elements that act as landmarks. aria-label, aria-labelledby, aria-describedby are generally ignored by assistive technologies when they are applied to generic HTML elements which do not have an interactive role e.g. button, link, input.

    The <div> container for the side navigational panel has aria-label="Side panel" attribute

    Recommendation

    The side navigation panel lacks the dialog role attributes. Remove the aria-label="Side panel" attribute from the <div> within the <nav>.

    Note:

    To determine whether a role and aria-* is invalid, consult the .

    Resources

    1

    5

    In smaller viewports or when zoomed in, content requires scrolling in two dimensions. Users with low vision will find it difficult to read and understand content.

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    To ensure content does not cause horizontal scrollbars or end up clipped, do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    1.4.10 Reflow AA

    1

    1

    7.3

    Third party content is not accessible

    Web authors are responsible for ensuring that all third party content is accessible, including:

    • content that is part of a service supplied by an external provider, such as ecommerce processes, online booking systems, product shipping tracking, and standalone user login systems.

    • content in the form of media supplied by an external provider, such as embedded videos from providers like YouTube and Vimeo, commercial news feeds, social media feeds, online entertainment feeds, games, tickers, and advertisements.

    • content supplied or generated by users, such as blog posts, comments, articles, images, and videos uploaded by users.

    The video is an embedded YouTube video and has several accessibility issues, including the following:

    • The Duke Energy logo is announced by screen readers as "photo image of Duke Energy".

    • There is low text contrast for white text when the video is also white or a light color.

    • The focus indicator is difficult to perceive.

    Recommendation

    • Remediate any issues you are able to address.

    • Tell third party content providers that they must make their content accessible.

    • Don't use providers of non-conformant third party content.

    Resources

    1

    5

    7.4

    The iframe title does not adequately describe its purpose

    The video is an <iframe> with the label "YouTube video player". This title does not let users know which video is in the <iframe>. Screen reader users do not have enough information to know whether or not they should enter the <iframe> content.

    Recommendation

    Provide a descriptive title for <iframe> elements when they contain content for a user.

    Code example (simplified)

    For <iframe> elements that do not display content to users (e.g. it is used for programmatic reasons), take the following steps: Use CSS display:none or the hidden attribute

    Example code indicating an <iframe> is not intended to be read.

    Or

    iframe.hidden { display:none; }

    Resources

    2.4.6 Headings and Labels AA

    1

    4

    Data tables can be very useful components for conveying complicated information. These components can be difficult or impossible to navigate for keyboard only and screen reader users when not built using established design patterns.

    The complex data table has the following issues:

    • No announcement when table is sorted.

    • No accessible name.

    • Contains parsing errors: the <tbody> element is improperly nested within the <table> element (after the <tfoot> element).

    • Direction of sort appears when the button is hovered but not when it's focused with a keyboard.

    Recommendation

    Ensure that data tables are well-formed:

    • For the table itself, use the <table> element (or an element with a table role for ARIA based tables). Note that the grid role is reserved for a widget that uses most of the same roles within it (identified below) but triggers a different interaction mode for screen reader users.

    • Ensure that the table does not have a presentation or none role.

    For this <table> element, also ensure:

    • Sorting changes are announced to users. Use role="status" on an element within the <caption> element.

    • Sort direction is visually available at all times, or at least available when the button has keyboard focus.

    To fix parsing issues:

    1. Add the and bookmarklets to your browser of choice.

    2. Open the page you wish to check, then activate the DOM checking bookmarklet.

    3. The results for the page will be displayed, filter results using the Check for WCAG 2.1 parsing bookmarklet

    Example of <caption> element with status update when table is sorted

    <table> <caption>News and Resources <span role="status">sorted by title ascending</span></caption> ... </table>

    Remediation this assertion will also fix violations for the following WCAG success criteria:

    • 4.1.3 Status Messages

    • 2.1.1 Keyboard

    • 4.1.1. Parsing

    1.3.1 Info and Relationships A

    1

    2

    8.3

    Focus order for show more button is not logical

    When a user clicks the "Show More" button below the table, new table rows appear above the button, but keyboard focus remains on the button. Non-sighted screen reader users might not realize more information has populated above the button.

    Recommendation

    When a user presses the "Show More" button, programmatically shift focus to the first new link in the table. If a user presses the "Show Less" button, keep focus on the button itself.

    Resources

    Understanding Success Criterion 2.4.3: Focus Orderarrow-up-right

    2.4.3 Focus Order A

    1

    2

    Data tables can be very useful components for conveying complicated information. These components can be difficult or impossible to navigate for keyboard only and screen reader users when not built using established design patterns.

    The data table has the following issues:

    • Does not use <th> elements. Uses headings instead.

    • No accessible name

    • Missing attributes: role(s), colspan, rowspan

    • Rows not correctly associated to headers

    • Some text is hard-coded as all caps in the HTML. This can lead screen readers to announce the words in acronyms and is more difficult to read for people with dyslexia.

    Recommendation

    Ensure that data tables are well-formed:

    • Use a <caption> element to label the table. For ARIA based tables, use an ARIA labeling method such as aria-labelledby.

    • Use a <tr> element for each row, or the row role for rows in ARIA based tables.

    Additionally, remove headings. We recommend using sentence case. If this is unachievable, then use sentence case in the HTML and use CSS text-transform to transform the text to uppercase.

    The following shows a simple HTML table:

    <table> <caption>Contact Information</caption> <tr> <td></td> <th>Name</th> <th>Phone#</th> <th>Fax#</th> <th>City</th> </tr> <tr> <td>1.</td> <th>Joel Garner</th> <td>412-212-5421</td>

    The following shows the same structure but achieved through ARIA table roles:

    </div>

    </div>

    </div> </div>

    1.3.1 Info and Relationships A

    1

    2

    Each video tile button derives its accessible name from its descendant image's alt attribute. The alt attribute indicates that the text describing its image i.g. "Parental Leave - Chris Bethany Mosteller image". This information is unnecessary; the text alternative is meant to describe the control's purpose. Superfluous information in the text alternative increase the page noise and make it harder to understand a control's purpose.

    Recommendation

    Good alternative text should describe an image's purpose more than its contents. Remove "image" from the alt attribute. The text alternative could be further improved by noting that it is a video.

    Simplified, recommended markup for a video tile button

    <button>

    </button>

    5

    4

    Button text, such as "get your bill online" and the "Close" button for expanded content is underlined when hovered by a mouse user. This visually signifies a link. Links navigate to a new location while buttons produce actions. If users click a link, they expect to navigate to another page or another part of the page. Clicking these controls expands and collapses content. This can create a confusing experience.

    Recommendation

    Remove the underline when the buttons are hovered. Use a different way of expressing hover state. For example, add a box around the button text when hovered.

    6

    0

    The "Awards" link has a title attribute with a long, incomprehensible string sitecore:item:InternalLinkTreeview_AC99B8D34494492B9AFA2BD36DF3DDC6, that resembles a file name, as its value. When focus is set to the link, screen readers announce this title attribute as part of the link's accessible name/description.

    Recommendation

    Remove the title attribute.

    Recommended (simplified) markup for the link with the title attribute removed

    <a href="/our-company/about-us/awards"> ... </a>

    Resources

    2.4.6 Headings and Labels AA

    1

    3

    13.3

    Flashcard is not keyboard accessible

    The links, "Giving Back", "Awards", "Our History", and "Diversity", have behavior that could be described as flashcards or flipboards. When the mouse moves over a card, it displays a description of the link. However, this interaction cannot be triggered via keyboard.

    It's important to note that the link description can be accessed by screen reader users, even when it is visually hidden. This is good behavior.

    Recommendation

    When focus is set to the link, display the description; extend the behavior for hover state to focus state.

    2.1.1 Keyboard A

    4

    3

    When the browser width is less than 750 CSS pixels, two of the tabs are offscreen. A mouse user cannot scroll through the list of tabs to view these two offscreen tabs, "REC Ownership" and "Low-Moderate Participation".

    This assertion is a best practice because issues with using a site via the mouse are primarily not covered under WCAG. However, regardless of whether the assertion is a WCAG error, the issue has a legitimate impact on the site's overall user experience.

    Recommendation

    There are multiple approaches which could be used.

    Option 1

    At smaller viewports, add a scrollbar to the tab panel. Mouse users can then select the scroll bar and move through the tab list.

    Option 2

    At smaller viewports, use an accordion tab rather than a tab panel.

    1

    3

    14.3

    Content requires both horizontal and vertical scrolling when resized to a width of 320 CSS px / height of 256 CSS px

    In smaller viewports or when zoomed in, content requires scrolling in two dimensions. Users with low vision will find it difficult to read and understand content.

    At smaller viewports, two of the six tab controls are offscreen, which requires horizontal scrolling within the tab list.

    Beyond the challenge for low-vision users, mouse users will face difficulty scrolling the tab list. It is not possible, using a mouse sans scroll wheel, to view the other tabs in the list. This aspect of the tab panel is discussed in a separate best practice assertion.

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    There are multiple approaches which could be used to make the tab panel reflow for smaller viewports. A scrollbar could be added to the tab list or the tab panel could have a responsive adaptation where it becomes an accordion tab.

    Resources

    1.4.10 Reflow AA

    1

    2

    Within the section with the heading "If I am making a payment dated today, can I cancel it after I have clicked the 'Submit' button?" there is a link without any text. This link is invisible on the page, but it is still announced to screen reader users. Because there is no associated text, the full URL is announced instead. This can be confusing and difficult to parse for screen reader users.

    Recommendation

    Remove the link, hide it from all users via the hidden attribute or CSS display:none, or add text and ensure it is available to all users.

    Recommended markup (if not removed)

    This also fails the following WCAG success criteria, which will also be resolved by remediation of this assertion: 4.1.2 Name, Role, Value

    Resources

    2.4.4 Link Purpose (In Context) A

    1

    3

    16.3

    Link name is ambiguous in isolation

    In the section with the heading, "How do I receive my $10 Restaurant.com dining certificate?" there is a link that is simply labelled "this page". Within the context of this paragraph, it's clear that this is a link to the terms and conditions page. Some screen reader users navigate using TAB or encounter all links within one list. Without the context of the surrounding text, these users will not know where the link "this page" leads.

    Recommendation

    Include the destination of the link within the link text itself.

    Recommended Markup (simplified)

    <p>... Please view our <a href="http://www.prizelabs.com/termsandconditions/" target="_blank">complete terms and conditions</a>. ....</p>

    1

    4

    Content that is revealed when a user interacts with a control does not follow immediately after the control that triggers its appearance, either in reading or tab order.

    When the "Show All" disclosure button is activated, the new content loads above the button. This is confusing for assistive technology users. When assistive technology users expand a widget, they expect the new content to be the next element on the page.

    Recommendation

    When a keyboard user activates a control (such as a button) that triggers the reveal or injection of previously hidden content (such as a new section in a form), they must be able to navigate quickly and easily to this added content. The new content must follow the trigger element in both the reading and tab order.

    The most straightforward solution here is to add the new content after the "Show All" button and not before the button.

    The button has an aria-expanded attribute to indicate whether the content it controls is expanded or collapsed. When the aria-expanded attribute is toggled, it triggers an announcement to assistive technology. This is positive behavior. The label also switches between "Show More" and "Show less". As a rule of thumb, one should not change an ARIA property and the label together. If the label changes, the button has already changed state in a sense. However, a change in label alone does not trigger an announcement to assistive technology.

    While it is not required, it is suggested to provide a name for the button which does not switch between "Show more" and "Show less". A suitable name could be "Plan disclaimer" or "Plan details".

    Simplified, recommended markup for expandable/collapsible widget with content following the disclosure button

    <p>These products or services are not part of the regulated utility services offered by Duke Energy Carolinas ("DEC") and ... </p>

    Resources

    2.4.3 Focus Order A

    1

    2

    When the browser is set to 1024 x 768 pixels, and zoomed to 200%, the content in the feedback widget becomes clipped off screen or overlaps other content. The effect is most notable when one scrolls down in the feedback widget and then attempts to scroll back to the top. The top of the feedback, including the button to collapse the widget becomes cut offscreen.

    Recommendation

    Make sure that all text responds to changes in text size settings, and that elements do not overlap or get clipped when resized. Ensure there is no loss of content or functionality when the site is zoomed to 200% and a mid-range screen size i.e. 1024 x 768 pixels.

    General recommendations on building responsive web applications:

    • Use em-based container and font sizes. Setting the size of containers using em units means that the container will adapt in size to reflect any changes in text size within those containers. In terms of text resizing, em units are more robust than px units, particularly when using Internet Explorer, which won't resize text that has been set using absolute measurements.

    • Use fluid widths for layouts. For example, if you set the width of the main container for your site to 100% (rather than, say, 960px), this will mean that the container will always span the length of the browser window, regardless of the size of this window. In other words, you limit horizontal scrolling.

    • Use responsive layouts. Using media queries, you can re-order the layout of content based on the size of the viewport. Consequently, if a user zooms into the page, or uses text resizing, the page will automatically adapt to the new viewport size.

    Resources

    1.4.4 Resize text AA

    1

    2

    19.3

    Landmark use is incomplete or incorrect

    The feedback widget has a generic <div> which has an inappropriate role="main" attribute. This element with role="main" which contains most of the widget's content. A page can only have one <main> landmark.

    Recommendation

    When using landmarks on a web page, ensure they are used correctly:

    • A page may only have one <main>

    In the case of the feedback widget, simply remove the <main> landmark and instead use a generic <div> as a container.

    1.3.1 Info and Relationships A

    1

    5

    19.4

    The iframe does not have a title that describes its purpose

    The feedback panel or aside is within an <iframe> lacking a label.

    Recommendation

    Provide a descriptive title for <iframe> elements when they contain content for a user such as the feedback panel.

    Code example (simplified)

    When an <iframe> elementdoes not display content to users (e.g. it is used for programmatic reasons), take the following steps: Use CSS display:none or the hidden attribute

    Example code indicating an <iframe> is not intended to be read.

    Or

    iframe.hidden { display:none; }

    4.1.2 Name, Role, Value A

    1

    4

    19.5

    Hidden content that affects understanding is announced by screen readers

    When the feedback widget is collapsed, its content is positioned offscreen but the content remains accessible to assistive technologies. Screen reader and keyboard-only users are able to interact with its content. That keyboard users are able to tab onto the content within the feedback widget is discussed in a separate 2.4.3 assertion.

    Recommendation

    When the widget is collapsed, ensure its content is not reachable via keyboard or assistive technologies. Apply display: none or visibility: hidden to the widget's container.

    1.3.2 Meaningful Sequence A

    1

    2

    19.6

    Control does not have an accessible name

    The image-based control to close the feedback aside lacks an accessible. The descendant image does not have a text alternative and there is no other method present to provide an accessible name.

    The control is a generic <div> element. Besides lacking an accessible name, the control also lacks the appropriate button role. This is discussed in a separate 4.1.2 assertion.

    Recommendation

    The accessible name is the text that identifies and describes the control to assistive technology users. It is programmatically exposed by the browser to assistive technology products (such as screen readers or speech recognition tools).

    There are many methods to provide an accessible namearrow-up-right for the form controls using native HTML elements/attributes or ARIA attributes.

    The control to open the feedback aside is a generic <div>. It lacks accessible name, role, and state information. There are numerous steps which must be undertaken besides providing an accessible name.

    • Mark up the control as a <button> or apply role="button"

    To provide an accessible name, either apply an appropriate alt attribute to the descendant <img>. Alternatively, when an image is the sole element within an interactive element, i.e. a button or a link, apply aria-label attribute to the functional container.

    Simplified, recommended markup for control to expand the feedback aside using plain HTML text

    Resources

    4.1.2 Name, Role, Value A

    1

    1

    19.7

    Buttons do not follow the established design pattern

    The feedback aside contains numerous controls which lack appropriate programmatic button roles.

    • The control to close the feedback aside acts like a button but the control is a generic <div> and does not convey the appropriate semantic information.

    • The control to capture the screen is marked up as a generic <span>.

    The control to close the feedback aside also lacks an accessible name; this issue is covered in a separate 4.1.2 assertion.

    Recommendation

    It is strongly preferred to use a <button> instead of coercing a generic <div> or <span> into a custom button. Native HTML comes with accessibility built into the element and does not require functionality to be rebuilt.

    If it is not possible to use native HTML, ARIA may be used to explicitly assign a role to a generic element.

    • Set role="button" on the generic <div> or <span>.

    • Include it in the focus order with tabindex="0".

    Resources

    4.1.2 Name, Role, Value A

    2

    3

    Images of text can be problematic for users with low vision, color blindness, and cognitive impairments. When text is presented as an image, it is not possible for users to change the presentation of the text to suit their particular needs and preferences (such as changing the font/typeface, changing the foreground/background color to increase contrast, increasing line height or spacing).

    In addition, when text is presented using bitmap images (JPEG, PNG, GIF), it generally becomes blocky, blurry and harder to read for users that use browser zoom or magnification software.

    The control to expand the feedback aside is a CSS-generated background image of text, "Feedback".

    Recommendation

    Authors are encouraged to use "real" text in their web pages rather than making images of text (<img> elements, CSS background images, rendered dynamically on <canvas> elements, SVG path data, etc.), unless the particular way in which text is visually presented is essential and/or cannot be achieved using regular technologies like HTML/SVG/CSS (including, but not limited to, the use of text-shadow, web fonts, gradient backgrounds, rounded corners).

    For "Feedback", the styling is not tied to branding such as a logo. The text is feasible to recreate using HTML and CSS. Therefore, it is recommended to use real HTML.

    1.4.5 Images of Text AA

    1

    4

    20.3

    Control does not have an accessible name

    The CSS-generated background image-based disclosure control to expand the feedback aside lacks an accessible name. The image does not have a text alternative.

    The control is a generic <div> element. Besides lacking an accessible name, the control also lacks the appropriate button role and state information to indicate whether the content it controls is expanded or collapsed. This is discussed in a separate 4.1.2 assertion.

    Recommendation

    The accessible name is the text that identifies and describes the control to assistive technology users. It is programmatically exposed by the browser to assistive technology products (such as screen readers or speech recognition tools).

    There are many methods to provide an accessible namearrow-up-right for the form controls using native HTML elements/attributes or ARIA attributes.

    The control to open the feedback aside is a generic <div>. It lacks accessible name, role, and state information. There are numerous steps which must be undertaken besides providing an accessible name.

    • Mark up the control as a <button> or apply role="button"

    • Apply aria-expanded attribute to the control and toggle its value to reflect whether the content it controls is expanded or collapsed.

    To provide an accessible name, the most straightforward best approach is to place real HTML text within the control rather than use an image of text. This solves the 1.1.1 Non-text Content, 1.4.5 Image of Text, and part of the 4.1.2 Name, Role, Value assertions.

    If it is not possible to use HTML text, alternatively, when an image is the sole element within an interactive element, i.e. a button or a link, apply aria-label attribute to the functional container.

    Simplified, recommended markup for control to expand the feedback aside using plain HTML text

    <span>Feedback</span>

    Simplified, recommended markup for control to expand the feedback aside using aria-label attribute

    Resources

    4.1.2 Name, Role, Value A

    1

    1

    20.4

    For elements with visible text/label, the accessible name does not contain the visible text

    Controls have an accessible name that does not match or contain the visible text label for the control. This will make it more difficult for speech interface users to identify and operate the controls.

    The control to expand the feedback aside has a CSS-generated background image of text "Feedback" but no text alternative that could provide an accessible name. Given the lack of an accessible name, it is not possible for the control's accessible name to match the visible text.

    Recommendation

    Ensure that all interface components that present the user with visible label text have an accessible name which matches, or at least contains, this visible text.

    Note: The visible label text needs to be part of the accessible name as visually displayed. It cannot be reordered, nor be modified to contain additional words in between the visible label. A control with the visible label "FOO BAR" can have an accessible name of:

    • "FOO BAR other words"

    • "other words FOO BAR other words"

    • "other words FOO BAR"

    But it cannot be:

    • "FOO other words BAR"

    • "BAR FOO"

    Resources

    2.5.3 Label in Name A

    1

    2

    20.5

    The disclosure control does not follow the established design pattern

    The control to expand the feedback aside has the following issues:

    • The disclosure control is a generic <div>. The control to reveal the disclosure content is not exposed as a button.

    • The disclosure control is a CSS-generated background image of text but lacks a text alternative. Partly as a result, the control lacks an accessible name

    • Does not have the correct states and properties, e.g. aria-expanded

    Recommendation

    • Use real HTML text within the control to provide an accessible name. Alternatively, apply aria-label to the functional container.

    • Preferably, mark up the control as a native HTML <button> or at least apply role="button" to the control

    Simplified, recommended markup for disclosure control

    <button aria-expanded="false"> <span>Feedback</span> </button>

    Resources

    4.1.2 Name, Role, Value A

    1

    2

    20.6

    Visually hidden elements receive keyboard focus

    When the feedback aside is collapsed, its content remains accessible to keyboard users. Keyboard users are able to tab onto the feedback aside and interact with its focusable elements when they are offscreen.

    aria-hidden="true" is applied to the feedback aside's container. However, ARIA only changes the announcement to screen readers and does not affect the functionality for any other user group. ARIA does not prevent an element from receiving keyboard focus.

    Recommendation

    When the feedback aside is collapsed, ensure that its content is hidden from assistive technologies and that focus cannot be set to its content. The most straightforward and best solution is to hide its content using display: none or visibility: hidden.

    If this is not possible, keep aria-hidden="true" attribute on the feedback aside container, when it is collapsed and apply tabindex="-1" to each focusable element within the container.

    2.4.3 Focus Order A

    6

    2

    20.7

    Hidden content that affects understanding is announced by screen readers

    When the feedback widget is collapsed, its content is positioned offscreen but the content remains accessible to assistive technologies. Screen reader and keyboard-only users are able to interact with its content. That keyboard users are able to tab onto the content within the feedback widget is discussed in a separate 2.4.3 assertion.

    Recommendation

    When the widget is collapsed, ensure its content is not reachable via keyboard or assistive technologies. Apply display: none or visibility: hidden to the widget's container.

    While a less favorable solution, apply aria-hidden="true" to the widget's container and tabindex="-1" attribute to all focusable elements within the widget, when it is visually hidden.

    1.3.2 Meaningful Sequence A

    1

    2

    The information about call-time preference is visually displayed as a description list. In this case, it's a question and then an answer. This content is not marked up as a list.

    When content is presented as a list, or a clearly related collection of items, marking it up using semantic list HTML elements helps assistive technology users to navigate and orient themselves within a page of content. Screen reader users can navigate to (or over) lists when they are correctly marked up. Also, when reading content in a list, assistive technology provides rich information about how long the list is and where the user is in the list. For example: "List, 9 items, Home, 1 of 9, About, 2 of 9".

    This particular list is confusing because it is displayed even when there is no time indicated, like when a user chooses email as the preferred method of communication.

    Recommendation

    Remove the list entirely and write this information as a sentence. "We will reach out to you by phone during business hours," for example. If the user indicates that they prefer email, either write a new sentence indicating that preference, "One of our representatives will email you shortly," or remove the sentence altogether.

    If you choose to keep the question and answer structure, use a description list (<dl>)for this data.

    Code example (simplified)

    <dl> <dt>If contact by phone is your preference, what time of day is best?</dt> <dd>Daytime</dd> </dl>

    Resources

    1.3.1 Info and Relationships A

    1

    5

    The checkbox is marked up as a list with one item in it and does not make sense as a list. The three terms and conditions below the checkbox visually appear as a list but are not marked up as a list.

    When content is presented as a list, or a clearly related collection of items, marking it up using semantic list HTML elements helps assistive technology users to navigate and orient themselves within a page of content. Screen reader users can navigate to (or over) lists when they are correctly marked up. Also, when reading content in a list, assistive technology provides rich information about how long the list is and where the user is in the list. For example: "List, 9 items, Home, 1 of 9, About, 2 of 9".

    Recommendation

    Remove the list semantics from the checkbox. Add unordered list semantics to the terms and conditions.

    Recommended Markup for terms and conditions

    <ul> <li>Duke Energy <b>Carolinas</b> - <a href="..." target="_blank">Gas Line Repair Program</a> (<a href="..." target="_blank">En Español</a>)</li> <li>Duke Energy <b>Indiana</b> - <a href="..." title="DE Indiana Gas Line Repair Program" target="_blank">Gas Line Repair Program</a> (<a href="..." target="_blank">En Español</a>)</li> <li>Duke Energy <b>Progress</b> - <a href="..." title="DE Progress Gas Line Repair Program" target="_blank">Gas Line Repair Program</a> (<a href="..." target="_blank">En Español</a></li> </ul>

    Additionally, this checkbox is within a <fieldset> element with an empty <legend> element. This is unnecessary and could cause confusion. Remove these elements.

    Remediating this assertion will also fix the following success criteria: 2.4.4 Link Purpose (In Context).

    Resources

    1.3.1 Info and Relationships A

    2

    4

    22.3

    Textbox relies on placeholder text for instructions

    Instructions for the "Comments" field are only available within the placeholder attribute.

    Text marked up using the placeholder attribute will generally be exposed to assistive technologies, so is announced by screen readers. But for other user groups, relying on placeholder text on its own for instructions is a problematic approach. Issues include:

    • Placeholder text disappears when a user starts typing. This means that there is no visible label when users are entering text into the input, which could affect users with short-term memory issues.

    • Placeholder text generally has poor contrast, and when its contrast is increased (and sometimes not even when it is increased) users may skip a field with placeholder text thinking the field has already been filled out. In this case, the placeholder text is gray (#a1a1aa) on a white (#fff) background. This only provides a contrast of 2.6:1, which does not pass the minimum 4.5:1 contrast ratio between foreground and background colors.

    Recommendation

    Include the character limit information outside of the <textarea> element. Associate this instruction text to the <textarea> by using the aria-describedby attribute.

    • Give the element that contains the instructions a unique id value.

    • Give the <textarea> element an aria-describedby attribute with a value that matches the value of the id in the previous step.

    Simplified Markup

    <label ... for="Comments">Comments</label>

    Remediating this assertion also addresses violations of the following WCAG success criterion: 1.4.3 Contrast (Minimum).

    Resources

    3.3.2 Labels or Instructions A

    1

    3

    22.4

    Required form controls do not visually convey the fact that they are required

    Users are required to input data before a form can be completed, but not all required fields are visually indicated.

    Recommendation

    Ensure required fields indicate their required state to users. This involves taking the following steps:

    • Indicate a field is required by including the text (required) or an asterisk _ in the label of the field

    • If an asterisk is used:

      • Include a message at the start of the form to explain the meaning of the asterisk, or

      • Hide it with an aria-hidden="true" attribute to ensure that screen readers do not announce the ambiguous "star" text when the associated field receives focus.

    • For screen reader users (if not complete already):

      • Apply the aria-required attribute to the <input> and set its value to true. This will ensure that the <input> is exposed to assistive technologies as a required field, and thus screen readers will announce this fact to their users (e.g. by announcing "required" after announcing the label and any contents within the field).

    Example markup for a required field

    <label for="employeeName">Employee <span aria-hidden="true">(required)</span></label>

    Another possible example markup for a required field

    <form> <p>Fields with an asterisk _ are required</p> ... <label for="employeeName">Employee *</label>

    ... </form>

    In this example, it is not necessary to hide the asterisk as users have been advised what it means. ^^^

    3.3.2 Labels or Instructions A

    4

    2

    22.5

    Invalid form controls must convey/expose the fact that they are invalid

    Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid.

    Recommendation

    Set aria-invalid="true" on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the aria-invalid attribute or set it to false.

    Setting aria-invalid

    <label for="address">Address</label>

    When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use aria-describedby to associate the error message with the form field. Once the field is valid, remove the error message.

    Setting aria-describedby to point to an error message

    <label for="email">Email</label>

    Resources

    4.1.2 Name, Role, Value A

    12

    3

    22.6

    Input fields collecting information about the user do not programmatically expose their purpose

    There are input fields asking for personal data which are not enabled to provide autocomplete information. This will make it more difficult for some users to enter their information.

    Recommendation

    Input fields collecting information about the user must programmatically expose their purpose. For web content, ensure that known/common inputs (such as name, home/business address, email, phone number, and any other common Input Purposes for User Interface Componentsarrow-up-right relating to personal user information) are identified explicitly in markup using relevant autocomplete values from HTML Living Standardarrow-up-right. Doing so enables user agents and tools (such as browsers, password managers, and assistive technologies) to provide useful functionality such as auto-filling fields with stored personal information (meaning users don't have to remember / enter this information manually).

    Specifying purpose using the autocomplete attribute

    ... <label for="passwordId">Password</label>

    1.3.5 Identify Input Purpose AA

    12

    3

    22.7

    Form instructions are in an illogical location and not announced by screen readers

    There are instructions within the form that are not associated with any form field. The instructions state that "South Carolina customers on Duke Energy Prepaid Advantage are not eligible for this service at this time." Users are not given this information until the middle of the form. Users who do not meet the requirements may have already filled in information. This can be a great burden to users with various types of disability.

    Additionally, many screen readers will enter "forms mode" when they enter this form. Any instructions not associated with a form field will not be announced by a screen reader at all.

    Recommendation

    Move this information to above the form. This will ensure it is announced by screen readers and that South Carolina customers on Duke Energy Prepaid Advantage do not unnecessarily fill out the form.

    Remediating this assertion will also fix violations related to the success criterion 1.3.1 Info and Relationships.

    Resources

    1.3.2 Meaningful Sequence A

    1

    3

    22.8

    Error messages not associated with controls in error

    Form control error messages must be correctly exposed.

    There are error messages for form controls that are not programmatically associated with their controls. Screen reader users may miss this information without the correct association.

    Recommendation

    Add an aria-describedby attribute to the form control which points to an id on the error message element. When the form is submitted, programmatically shift keyboard focus to the first field in error.

    Associating a form control error message

    <label for="address">Address</label> <input type="text" id="address" aria-invalid="true" aria-describedby="address-error"> <span id="address-error">Error: please enter a valid address</span>

    Remediating this assertion will also solve for violations of the following success criterion: 1.3.1: Info and Relationships

    4.1.2 Name, Role, Value A

    1

    1

    In smaller viewports or when zoomed in, content requires scrolling in two dimensions. Users with low vision will find it difficult to read and understand content.

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    To ensure content does not cause horizontal scrollbars or end up clipped, do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    1.4.10 Reflow AA

    1

    4

    23.3

    The parsed DOM of the page has errors that may prevent assistive technology from correctly interpreting content

    The DOM contains elements with the same id attribute values. Specifically, the check marks for complete steps all contain <path> elements with the same id attribute value: "check_svg__a".

    Recommendation

    Ensure all content is structured correctly using the most semantically appropriate elements. Ensure markup has complete start and end tags, is nested correctly according to the specification, has well-formed attribute values, and that id="..." attribute values are unique for the page.

    1. Add the and bookmarklets to your browser of choice.

    2. Open the page you wish to check, then activate the DOM checking bookmarklet.

    3. The results for the page will be displayed, filter results using the Check for WCAG 2.1 parsing bookmarklet

    Resources

    • bookmarklet

    4.1.1 Parsing A

    1

    5

    23.4

    Unordered list used for ordered content

    The form steps are presented as an unordered list. Unordered lists are only appropriate for list content for which the order of the list items is irrelevant. In this case, the list item order is important for understanding the structure of this list and the form.

    Recommendation

    Use an ordered list, <ol> in place of the unordered list, <ul>.

    Code example

    <li class="..."> ... <div class="..." style="...">Applicant Information</div> </li> ...

    Resources

    1.3.1 Info and Relationships A

    1

    0

    23.5

    Form steps have low contrast between foreground and background

    The incomplete form steps are represented as light gray (#D7D9DA) circles against a white (#FFFFFF) background. This only provides a contrast ratio of 1.4:1. Technically, this passes the minimum requirements for 1.4.11: Non-text contrastarrow-up-right because there is also text that represents these steps. However, the form steps are more difficult to perceive and understand using text alone.

    Recommendation

    Provide enough contrast between the foreground color and background color (or, more generally, "adjacent" colors that need to be distinguishable in order to correctly perceive/understand the graphical elements or their component parts) so that people who are color-blind or with moderately low vision can sufficiently distinguish important content. The contrast ratio must be at least 3:1.

    1

    0

    23.6

    Current form step is communicated visually but not programmatically

    The current step in the form is indicated with a hollow green circle. There is no alternative text to let assistive technology users, like screen reader users, know which step they are currently on.

    Recommendation

    Write "current step" in text above or below the step name. This may be visible to all users or visually hiddenarrow-up-right to that it is announced by screen readers but not visible on the page. Ensure that this text is removed when the step is no longer current.

    Recommended markup

    <div class="..."> Applicant Information <span class="visually-hidden">Current Step</span> </div>

    1.1.1 Non-text Content A

    1

    3

    23.7

    Focus order is not logical

    When a user presses continue to move on to the next step of the form, the page scrolls up visually, but keyboard focus remains at the bottom of the page. A keyboard user will have to navigate backward through the page to fill out the form.

    Non-sighted screen reader users will not know that new content above the continue button has changed. They might not know that any change has occurred and will have to navigate back through the page to find any changes. This can be confusing and disorienting for screen reader users.

    Note: This assertion is outside of scope for this component but was found during testing.

    Recommendation

    When a user presses the continue button and more content is loaded, programmatically shift focus to the text of the current step. Follow recommendations outlined in the assertion titled "Current form step is communicated visually but not programmatically" so that the text "current step" is also announced.

    Add tabindex="-1" to the <div> that contains this text. This will ensure that it can receive keyboard focus across browsers.

    Recommended markup

    <div tabindex="-1" class="..."> Schedule <span class="visually-hidden">Current Step</span> </div>

    Manually check each page by using TAB or SHIFT+TAB to move forward or backward through actionable elements. If the tab sequence is not as expected, take steps to correct this by re-ordering the HTML sequence so that a logical order is achieved. When the continue button is pressed, focus must return to the top of the page.

    Resources

    2.4.3 Focus Order A

    1

    2

    The Date Picker control has the following issues:

    • The calendar feature lacks accurate roles to let screen reader users know how to interact with it. For example, the calendar is broken into two grid features rather than one. The date picker is also missing dialog semantics.

    • The year input is named "current year" using the aria-label attribute. This is misleading and can result in screen reader users assuming they cannot choose a future year.

    • Focus does not return to the button when the date picker is closed.

    Recommendation

    • Follow an established accessibility pattern to correctly implement the grid calendar feature.

    • Use a visible and persistent "Year" label for the year input and remove the aria-label.

    Remediating this assertion will also fix violations of the following success criterion: 2.4.6 Headings and Labels.

    Resources

    4.1.2 Name, Role, Value A

    1

    5

    24.3

    Form controls that expect user input do not have appropriate instructions

    There are no instructions for date format on fields requesting dates. Users who type into the <input> rather than opening the date picker might input date information incorrectly. For example, someone might expect the date format to be dd/mm/yyyy. This could mean the difference between making an appointment for January 12th, 2023 (01/12/2023) and December 1st, 2021 (12/01/2023).

    Recommendation

    Form controls that require user input must have an appropriate label that describes the expected input to all users. For form controls that have additional restrictions or input requirements (such as requiring a specific format or a specific piece of information that is not obvious from the label alone), some form of instruction must be present to inform the user of these specific restrictions or requirements. This information must be permanently available and cannot be removed once a value is entered in the control.

    Ensure that instructions are programmatically associated with the <input> element using the aria-describedby attribute.

    Simplified code

    <input aria-describedby="date-format1"> <span id="date-format1">Date format: mm/dd/yyyy</span>

    Alternatively, add the date format to the <label> itself.

    Resources

    3.3.2 Labels or Instructions A

    5

    2

    In smaller viewports or when zoomed in, the arrow that indicates the alert can be expanded is obscured by the feedback tab. Visual users might not realize this content can be expanded.

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    Do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    1.4.10 Reflow AA

    1

    3

    25.3

    Headings are not marked up as headings

    The headings for alerts are indicated visually with bold text but are not marked up as headings.

    Recommendation

    Ensure headings are defined correctly:

    • Use HTML heading markup (<h1> to <h6>) to semantically identify headings.

    • Only if <h1> to <h6> elements cannot be used, role="heading" and aria-level attributes can be used. The value of the aria-level attribute must be a number ranging from 1 to 6, indicating the heading's level.

    • Only use a heading as a descriptive label for the section of content that follows it. Do not use headings solely for visual styling.

    Resources

    1.3.1 Info and Relationships A

    1

    3

    25.4

    Labels provided for accordion buttons are not sufficiently descriptive

    The global alert feature is a <button> element named with the aria-label attribute. Using this aria-label overwrites the entire content of the alert. Screen reader users will not have access to any of the text within the alert. Instead, screen readers will announce the presence of a "toggle" button. Clicking the button does not provide access to the rest of the content.

    This can be confusing for screen reader users, while also severely limiting access to alerts. Speech-input users will have difficulty toggling the button because the visible label does not match the programmatic label.

    Recommendation

    • Remove the aria-label attribute.

    • Remove the truncated description from the button, and only reveal this content when the button is pressed, and the content is expanded. The accessible name of the button will now be the name of the alert icon and the name of the alert.

    Remediating this assertion also resolves violations of the following WCAG success criterion: 2.5.3. Label in Name.

    Resources

    2.4.6 Headings and Labels AA

    1

    1

    25.5

    The accordion does not follow the established design pattern

    The Accordion custom control has the following issues:

    • Non-descriptive accessible name (see the assertion titled "Labels provided for accordion buttons are not sufficiently descriptive").

    • For screen reader users, there is no expanded content. The only content that is revealed visually is extra text within the button itself. This text is not announced by screen readers due to the aria-label on the button. If you remove the aria-label, screen readers will announce the full text of the button without truncation. There are two problems with this. First, it creates a very long name for the button. Second, expanding the button does not reveal more content for screen reader users. This can be confusing. Screen reader users might assume the button is broken.

    • There are visual headings for the accordion, but the headings are not programmatically defined.

    • It is unclear whether or not the headings will be sufficiently descriptive.

    Recommendation

    • See the assertion "Labels provided for accordion buttons are not sufficiently descriptive" to fix the accessible name.

    • Remove the description of the alert (the text that begins "From 11 p.m. Saturday, Sept. 25...") from the button itself. Reveal it only when the button is pressed and the accordion is expanded.

    • Change the bold text (in this case, "Test Alert") to a heading.

    Simplified code example

    <h2> <button aria-expanded="false" aria-controls="alert1" id="alert1btn"> <img src="..." alt="alert"> System maintenance September 25th and 26th </button> </h2> <div id="alert1" role="region" aria-labelledby="alert1btn">From 11 p.m., Saturday, Sept. 5...</div>

    Resources

    4.1.2 Name, Role, Value A

    1

    1

    The straplines for each card have been marked up as headings even though, for the three smaller cards, they do not head up any subordinate content. For example, the text that says "Help people in need with your monthly Duke Bill" does not head up any other content but has been marked up as an <h4> heading.

    Recommendation

    Do not use heading markup for text that does not serve as a descriptive label for the section of content that follows it.

    The text in the three smaller cards should not be marked up as headings. Rather than using <h4> elements we would recommend wrapping the text in a <p> tag and using CSS to create the required look and feel.

    We would also recommend marking up the category descriptions above the straplines as headings. For example, the text that says "CUSTOMER ASSISTANCE PROGRAMS", above "Help people in need with your monthly Duke Bill", should be marked up as a heading.

    Recommended markup (simplified) for card heading and text

    <h4 class="text-xs font-bold uppercase">customer assistance programs</h4>

    <p class="flex-1 mt-8 text-xl text-gray-dark ">Help people in need with your monthly Duke Bill</p>

    Note: Also see the following related issues:

    • Link text does not indicate the purpose of the link

    • Learn More controls can be used with a mouse but are not reachable and operable with the keyboard

    1.3.1 Info and Relationships A

    3

    3

    The "Zip code" field is not enabled to provide autocomplete information. This will make it more difficult for some users to enter their zip code.

    Recommendation

    Input fields collecting information about the user must programmatically expose their purpose. For web content, ensure that known/common inputs (such as name, home/business address, email, phone number, zip code, and any other common Input Purposes for User Interface Componentsarrow-up-right relating to personal user information) are identified explicitly in markup using relevant autocomplete values from HTML Living Standardarrow-up-right. Doing so enables user agents and tools (such as browsers, password managers, and assistive technologies) to provide useful functionality such as auto-filling fields with stored personal information (meaning users don't have to remember / enter this information manually).

    Specifying purpose using the autocomplete attribute

    1.3.5 Identify Input Purpose AA

    1

    2

    27.3

    Corrections or suggestions for errors are not provided

    An erroneous zip code value was found and displayed in text, but that error message did not provide a suggested value or pattern. If not enough digits were entered, for example, the error did not describe why the data was invalid or how to fix the problem.

    Recommendation

    When a component automatically identifies an erroneous value, provide the user with a suggestion for resolving the problem. Examples of useful suggestions are:

    • Reminder of required state, e.g. "This field is required"

    • A reminder of the expected format, e.g. "Please enter the date in the MM/DD/YY format"

    • A prompt as to the expected number of digits in a zip code, e.g. "Not enough digits: zip codes are made up of 5 digits".

    • A correction of the erroneous value, e.g. "‘Botson' is not a recognized city name. Did you mean ‘Boston'?"

    Resources

    3.3.3 Error Suggestion AA

    1

    2

    27.4

    Form controls rely on placeholder text for visual label

    The form controls to enter a zip code relies on placeholder text for its visual label.

    • Placeholder text disappears when a user starts typing. This means that there is no visible label when users are entering text into the input, which could affect users with short-term memory issues.

    Recommendation

    Provide an always visible and programmatically associated label for the form control to select a zip code.

    Resources

    3.3.2 Labels or Instructions A

    1

    3

    The search panel looks and behaves like a modal dialog in that it dims the underlying content. However, it does not present to assistive technologies as a dialog and has the following issues:

    • Lack of a role="dialog" attribute.

    • Lack of an accessible name.

    • Pressing ESCAPE on the keyboard does not dismiss the dialog.

    • Keyboard focus is not properly constrained to the modal dialog while it is active.

    • When the search panel is collapsed, focus does not return to the "Search" button which triggered the panel to open.

    • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing.

    Recommendation

    • For dialogs with a visible title, such as the search panel, use an aria-labelledby reference to the element containing the title text (usually the <h2>), e.g. "Search" to provide an accessible name.

    • For dialogs that do not have a visible title, use aria-label to provide an accessible name for the dialog.

    Resources

    4.1.2 Name, Role, Value A

    1

    2

    28.3

    Focus is not managed within the search dialog

    Keyboard focus is not managed well with regard to this dialog:

    • Focus is not constrained within the dialog, but can be moved to dimmed elements on the underlying page.

    • when the dialog is dismissed, focus is not moved to the trigger that caused the dialog to be invoked.

    Recommendation

    See the related issue entitled "The search panel does not follow the established modal dialog design pattern" for a full solution.

    2.4.3 Focus Order A

    1

    2

    28.4

    Inappropriate navigation landmark

    A <nav> serves as the container for the search side panel. This is inappropriate. A navigation region should only contain navigation controls.

    Recommendation

    Remove the <nav> as a container. The container for the search results panel must have role="dialog". This is discussed at length in other assertions for this component.

    1.3.1 Info and Relationships A

    1

    3

    Keyboard focus is not managed well with regard to this dialog:

    • Focus is not moved to the dialog or one of its interactive descendants when it is displayed.

    • Focus is not constrained within the dialog, but can be moved to dimmed elements on the underlying page.

    • when the dialog is dismissed, focus is not moved to the trigger that caused the dialog to be invoked.

    Recommendation

    See the related issue entitled "The panel does not follow the established modal dialog design pattern" for a full solution.

    2.4.3 Focus Order A

    1

    2

    Keyboard focus is not managed well with regard to this dialog:

    • Focus is not moved to the dialog or one of its interactive descendants when it is displayed.

    • Focus is not constrained within the dialog, but can be moved to dimmed elements on the underlying page.

    • when the dialog is dismissed, focus is not moved to the trigger that caused the dialog to be invoked.

    Recommendation

    See the related issue entitled "The panel does not follow the established modal dialog design pattern" for a full solution.

    2.4.3 Focus Order A

    1

    2

    Labels which do not sufficiently describe the functionality of an interactive control, such as links, buttons and form controls, make it difficult for users to understand the purpose of the control.

    Some links on nav cards take the user to unexpected pages. The "Building a Smarter Energy Future‌" link, for example, leads to a generic Products and Services page; while the "ESG is essential to who we are‌" link leads to a page containing a 404 error. This may well be because the site is under development.

    Recommendation

    The visible text, or alternative text, used to label interactive elements must clearly and concisely describe the control's function or purpose.

    The "Building a Smarter Energy Future‌" link, for example, should describe the page, or place in the page, the user will see after activating the link. If linking to the Products and Services page, the link text should say "Products and Services." Or if linking to a specific heading on the destination page, the link text should mirror the heading.

    Resources

    2.4.6 Headings and Labels AA

    4

    3

    The decorative image, of hands clasped into the shape of heart, besides the heading, does not convey meaningful information. It has redundant and inappropriate text alternative "icon charity base teal dark x". Having text alternative for decorative images increase the page's verbosity. This causes "page noise" for screen reader users. Navigating the site is more confusing and laborious.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    Text alternatives for imagesarrow-up-right

    1.1.1 Non-text Content A

    1

    4

    Controls lack a visual indication of focus. As a result, it is not possible to visually determine which element is currently focused. Keyboard only users will face hurdles when navigating the site.

    In Firefox, the links ("Pay My Bill", "Start/Stop Service", "Outages", "Financial Help" have no visual indication of focus. In Safari, the focus indicator is faint; Safari's default focus indicator has extremely low color contrast against the image-based background.

    Recommendation

    All focusable elements must have a visible focus indication. For instances where this focus indication is not present or has been suppressed, ensure that an appropriate focus indication is provided.

    To further improve the user experience - and to ensure that focus indication is consistent across different browsers and their often quirky handling of default focus outline - define an explicit :focus style which is more visually evident (such as an explicit dark background, a bold border, or similar).

    Basic example of how to define explicit focus styles in CSS which apply to all focused elements

    :focus { outline: 2px dotted black; / example values only / }

    When there are existing styles defined for mouse :hover, an initial step can be to "double-up" the CSS selector to also apply to :focus. In the case of the links within Top Tasks, it is suggested to use the controls' hover state - the control bounces up when the mouse hovers it.

    Basic example of "doubling-up" an existing :hover style to also apply on :focus

    Resources

    2.4.7 Focus Visible AA

    4

    2

    In smaller viewports or when zoomed in:

    • Sticky content obscures elements in the component

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    Do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    1.4.10 Reflow AA

    1

    2

    The "Watch Video" control is coded as a link (<a>), but it should be a button (<button>).

    The role of an element carries an implicit understanding of what the result will be of interacting with that control. For instance, buttons are used to perform in-page actions, such as closing a modal dialog, or to submit a form/data, and links are used to navigate from one page to another, or to fetch data (such as downloading a document).

    When incorrect roles are used (or omitted), assistive technology users may be confused by the resulting action of interacting with that control.

    Recommendation

    Links acting as buttons

    Wherever possible, it is always better to use native HTML <button>, <input type="button" ...> or <input type="submit" ...> elements (depending on their specific purpose) for controls acting as buttons, as they're understood correctly by assistive technologies.

    If it is not possible to use an actual <button> element, ensure that at least the appropriate programmatic role="button" is exposed to assistive technologies. In addition, ensure that the control can be operated like a regular <button> and can be activated using both ENTER and SPACE keys (this will potentially require a small change to the current JavaScript behavior).

    Resources

    1

    5

    38.3

    The modal dialog does not follow the established design pattern

    The dialog has the following accessibility issues:

    • Lack of an accessible name.

    • Lack of an accessible description.

    • Lack of the expected role.

    • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing.

    • Keyboard focus does not return to the control that opened the dialog when the dialog is closed.

    • There are major parsing issues with this modal dialog that make it impossible to read with some assistive technologies. For instance, the JAWS screen reader. This is due to several nested elements with role="button" and the presence of aria-hidden.

    Recommendation

    • For dialogs with a visible title, use an aria-labelledby reference to the element containing the title text (ideally an <h2>) to provide an accessible name.

    • Use aria-label to provide an accessible name.

    Remediating this assertion will also address violations of the following WCAG success criteria:

    • 2.4.3 Focus Order

    • 1.3.1 Info and Relationships

    • 1.3.2 Meaningful Sequence

    Resources

    4.1.2 Name, Role, Value A

    1

    2

    38.4

    Iframe label is not sufficiently descriptive

    Labels which do not sufficiently describe the functionality of an interactive control, such as buttons and form controls, make it difficult for users to understand the purpose of the control. The <iframe> elements that contain videos are titled "YouTube video player." This doesn't give screen reader users any information about which video is within the <iframe>.

    Recommendation

    Provide a descriptive title for <iframe> elements when they contain content for a user.

    Code example (simplified)

    Resources

    2.4.6 Headings and Labels AA

    1

    4

    All of the links that open a PDF also open in a new tab or window, but do not inform the user of this action.

    Recommendation

    When a link opens in a new tab or window, indicate this as part of the link text. The most common approach is to add an icon to the link with an 'opens in new window' text alternative.

    Resources

    • Giving users advanced warning when opening a new windowarrow-up-right

    4

    4

    39.3

    Text overlaps when resized to a width of 320 CSS px / height of 256 CSS px

    In smaller viewports or when zoomed in:

    • Sticky content obscures elements in the component

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    Do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    1.4.10 Reflow AA

    1

    2

    39.4

    List markup is missing

    The list of links is presented as an unordered list, but not marked up as a list.

    When content is presented as a list, or a clearly related collection of items, marking it up using semantic list HTML elements helps assistive technology users to navigate and orient themselves within a page of content. Screen reader users can navigate to (or over) lists when they are correctly marked up. Also, when reading content in a list, assistive technology provides rich information about how long the list is and where the user is in the list. For example: "List, 9 items, Home, 1 of 9, About, 2 of 9".

    Recommendation

    Mark up unordered lists using <ul>, and <li> elements.

    Simplified code

    <ul> <li>Read <a href="..." target="_blank">2021 Terms and Conditions</a></li> <li>Read about <a href="..." target="_blank">2021 NC Solar Rebate Program</a></li> <li><a href="..." target="_blank">2021 Solar Rebate Rider SRR</a></li> <li><a href="...">Learn more about solar rebates for your business</a></li> </ul>

    Resources

    1.3.1 Info and Relationships A

    1

    3

    39.5

    Link has appearance of button

    The View Eligibility link has the appearance of a button. A keyboard user who presses SPACE expecting to activate the "button" will actually scroll the page.

    Recommendation

    The visual appearance of links should be standardized across the site so that keyboard users are not confused by which keys to use.

    Resources

    Links, Buttons, Submits, and Divsarrow-up-right

    1

    0

    In smaller viewports or when zoomed in:

    • Sticky content obscures elements in the component

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    Do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    Resources

    1.4.10 Reflow AA

    1

    2

    Slides 1 and 3 both have images with alternative text of "tweet image". These decorative images do not convey information and are not hidden from screen reader users. This can cause confusion for screen reader users.

    Recommendation

    Use an empty string as text alternative alt="". This will ensure that the image is not announced by assistive technologies.

    Recommended (simplified) markup

    Resources

    Text alternatives for imagesarrow-up-right

    1.1.1 Non-text Content A

    2

    4

    41.3

    A color contrast ratio of 3:1 is not provided for graphical objects used in controls

    The previous and next chevrons to change slides are graphical UI objects that do not meet the minimum contrast ratio of 3:1.

    Recommendation

    For icons used in custom controls (such as a triangle icon indicating a dropdown / disclosure button, or a checkmark icon indicating a checked checkbox), provide enough contrast between the foreground color and background color. More generally, "adjacent" colors need to be distinguishable in order to correctly perceive/understand the graphical elements or their component parts. This way people who are color-blind or have moderately low vision can sufficiently distinguish important content. The contrast ratio must be at least 3:1.

    1.4.11 Non-text Contrast AA

    2

    2

    41.4

    Changing text spacing results in a loss of content or functionality

    Content is obscured or functionality is degraded when text spacing styles are changed. Users with low vision or cognitive disabilities, may need to restyle text to make it easier to read (by increasing style properties such as line height, letter spacing and word spacing)

    Note that even prior to changing text spacing, the text "FOLLOW US ON TWI..." is the only visible link text present. Although the full text (Follow us on Twitter) is available for screen reader users, it is not available for sighted users and does not provide adequate context on its own.

    Recommendation

    No loss of content or functionality must occur when setting the following text style properties and by changing no other property:

    • line height (line spacing) to at least 1.5 times the font size;

    • spacing following paragraphs to at least 2 times the font size;

    • letter spacing (tracking) to at least 0.12 times the font size;

    • word spacing to at least 0.16 times the font size

    This also fails the following WCAG success criterion, which will also be resolved by remediation of this assertion: 2.4.4 Link Purpose (In Context).

    Resources

    1.4.12 Text Spacing AA

    1

    3

    41.5

    Text overlaps when resized to a width of 320 CSS px / height of 256 CSS px

    In smaller viewports or when zoomed in:

    • Text overflows its container, causing the text to become unreadable or unreachable for users.

    • Sticky content obscures elements in the component

    Recommendation

    Ensure that all content adapts and reflows to a viewport width of 320 CSS pixels without loss of information or functionality, and without requiring both horizontal and vertical scrolling. An exception is content which requires two-dimensional layout for usage or meaning, such as data tables.

    Do not use large fixed-width elements. Use CSS techniques such as relative units of measurement, flexbox layout, and/or media queries to display all content while achieving appropriate styling in small and large screens.

    Example of using relative units of measurement for a primary content area

    .content { width: 90%; }

    Example of using for more specific styles

    <link rel="stylesheet" href="basic.css"> <link rel="stylesheet" media="(max-width:320px)" href="small.css"> <link rel="stylesheet" media="(max-width:600px)" href="medium.css"> <link rel="stylesheet" media="(min-width:601px)" href="large.css">

    Note: CSS techniques like these achieve responsive web design goals while allowing desktop browser users to zoom in to 400%.

    When using sticky content, such as fixed header or footer, ensure that it is not possible for this content to completely obscure an element when it receives focus. The only way to achieve this is through scripting. A possible strategy could be:

    1. Set up a focusin event handler for the entire page.

    2. In the event handler, compare the bounding box of the focused element to the bounding box of the sticky container.

    3. If the focused element is positioned behind the sticky container, use the window.scrollBy method to scroll it into view.

    For a functioning demo, see

    This also fails the following WCAG success criterion, which will also be resolved by remediation of this assertion: 2.4.7 Focus Visible.

    Resources

    1.4.10 Reflow AA

    1

    2

    41.6

    Headings are not marked up as headings

    The Twitter icon is a visual heading that is not marked up as such in the HTML

    Recommendation

    Ensure headings are defined correctly:

    • Use HTML heading markup (<h1> to <h6>) to semantically identify headings (after adding the alternative text, per another assertion).

    • Only if <h1> to <h6> elements cannot be used, role="heading" and aria-level attributes can be used. The value of the aria-level attribute must be a number ranging from 1 to 6, indicating the heading's level.

    • Only use a heading as a descriptive label for the section of content that follows it. Do not use headings solely for visual styling.

    Resources

    1.3.1 Info and Relationships A

    1

    3

    41.7

    The carousel does not follow the established design pattern

    The carousel widget has the following issues:

    • The carousel does not have the correct roles.

    • The carousel does not have an accessible name.

    • The carousel lacks a group label.

    Recommendation

    Use of carousels is generally discouraged due to their common accessibility barriers. However, carousels can be made accessible, provided purposeful consideration to accessible UX best practices are followed. For example, the following steps can be used for an accessible carousel:

    • Wrap the carousel (including both its slides and controls) in a container with either a group or region role. In addition, add aria-roledescription="carousel" to the container.

    • Provide an accessible name for the carousel container using aria-labelledby (if there already is a visible heading or label), or aria-label (if there is no visible label).

    This also fails the following WCAG success criteria, which will also be resolved by remediation of this assertion: 1.3.1 Info and Relationships.

    Resources

    4.1.2 Name, Role, Value A

    1

    3

    41.8

    Link opens in a new window or tab

    The links in the carousel open in a new tab or window, but do not inform the user of this action.

    Recommendation

    When a link opens in a new tab or window, indicate this as part of the link text. The most common approach is to add an icon to the link with an 'opens in new window' text alternative.

    Resources

    • Giving users advanced warning when opening a new windowarrow-up-right

    1

    4

    41.9

    List markup is incorrect

    The carousel content is marked up as a list when it is not a list. Although there are three slides in the carousel, because two of them are hidden at all times the list is announced as an item of 1.

    Recommendation

    In this case it is recommended that the list markup be removed.

    Resources

    • Using ol, ul and dl for lists or groups of linksarrow-up-right

    1.3.1 Info and Relationships A

    1

    4

    When radio buttons, checkboxes and other related form controls are visually presented as being grouped, the grouping relationship must also be programmatically exposed.

    The set of US states is not semantically grouped. The text "Select your location" is not exposed as the overarching group label for the set of US states.

    Recommendation

    The simplest technique for exposing a series of form controls as a group, with a label is to nest the related controls in a <fieldset> element. Next, provide the group a label using the <legend> element. Note that a <legend> element must appear as the first child element of the <fieldset>.

    If it is not practical to use the <fieldset> and <legend> elements, an ARIA role="group" may be specified on the containing element, and an aria-labelledby may be used to reference the group's visible label. If a grouping does not have a visible label, then add one.

    Simplified, recommended markup for set of US states using ARIA aria-labelledby

    <p>Our site is customized based on your location. We'll remember your selection for future visits.</p>

    <li><button>North Carolina</button></li> <li><button>South Carolina</button></li> <li><button>Ohio</button></li> <li><button>Kentucky</button></li> <li><button>Indiana</button></li> <li><button>Florida</button></li> </ul>

    Resources

    1.3.1 Info and Relationships A

    1

    2

    44.3

    Lack of dialog focus management techniques

    The Jurisdiction Intercept has the behavior of a dialog. The component dynamically appears and a visually dimmed overlay covers main page content. However, the component has a total lack of the focus management techniques required for dialogs.

    • When the dialog appears, focus is not set to the component. Screen reader users are unaware that the dialog is present on the page; it is the last element on the DOM. A screen reader user would have to read through all page content to reach the dialog.

    • Focus is not constrained to the dialog. A keyboard user is able to interact with content under the visually dimmed overlay. As a result, it is not possible to determine what element is currently focused.

    Recommendation

    When the Jurisdiction Intercept dialog is launched, focus must be moved to the dialog. There are multiple acceptable focus targets.

    The safest option is to set focus to the Jurisdiction Selector's container. The component currently lacks dialog markup. Apply role="dialog" to the container. To accommodate legacy browsers, apply tabindex="-1" to the element with role="dialog". Also apply aria-labelledby attribute to the dialog container and reference the top-level heading "Select your location". This requires adding an id to the heading.

    The dialog must contain focus. Using the TAB key should cycle through the focusable elements within the dialog.

    This assertion covers the component's lack of focus management; the lack or ARIA and role attributes is reported in a separate assertion.

    Resources

    2.4.3 Focus Order A

    1

    2

    44.4

    The modal dialog does not follow the established design pattern

    The Jurisdiction Intercept component has the following issues:

    • Lack of expected dialog role.

    • Dialog container lacks an accessible name.

    • Neither the dialog, nor a descendant of the dialog is focused when the dialog is displayed. As a result, screen reader users are unaware that the dialog has appeared. This is reported in a separate 2.4.3 assertion.

    • Content in the background is still announced by and reachable to assistive technology while the modal dialog is showing.

    Recommendation

    • Add the role dialog to the containing element.

    • Ensure that the dialog or a descendant element of the dialog is focused when it is displayed. For dialogs with a visible title, use an aria-labelledby reference to the element containing the title text (ideally an <h2>) to provide an accessible name. The heading "Select your location" can be used as the visual title. This requires adding an id to the heading. If it is not possible to use the heading, then an aria-label

    Resources

    4.1.2 Name, Role, Value A

    1

    3

    When a pagination control is activated, the search results container dynamically updates without a full page reload. This change in page content does not trigger an announcement to assistive technologies.

    Recommendation

    Ensure that dynamic changes in content trigger an announcement to assistive technology.

    The best and most straightforward solution is create a live region to summarize/describe the change in search results.

    Adjacent to the pagination controls, there is text stating the page and number of results being displayed. The container for this text could be made into a live region.

    Apply aria-live="polite", aria-atomic="true", and role="status" to the container for this text.

    If using this existing text is not possible, then a separate live region could be created elsewhere on the page - the live region can be visually hidden.

    Simplified, recommended markup for live region

    <span>Search Results</span> <span>1 - 10 of 849 results</span> </div>

    4.1.3 Status Messages AA

    1

    3

    The current state of modal dialog accessibilityarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    Web Developer Extension for Firefoxarrow-up-right
    YouTube: MSFTEnable - Introduction to Disability and Accessibility (audio described version)arrow-up-right
    Text alternatives for imagesarrow-up-right
    Text alternatives for imagesarrow-up-right
    Text alternatives for imagesarrow-up-right
    4.1.2arrow-up-right
    Text alternatives for imagesarrow-up-right
    Text alternatives for imagesarrow-up-right
    Text alternatives for imagesarrow-up-right
    <desc>arrow-up-right
    aria-describedbyarrow-up-right
    Giving users advanced warning when opening a new windowarrow-up-right
    aria-hiddenarrow-up-right
    Text alternatives for imagesarrow-up-right
    aria-hiddenarrow-up-right
    Text alternatives for imagesarrow-up-right
    Check serialized DOM of current page (DOM Checking)arrow-up-right
    Check for WCAG 2.1 parsing compliancearrow-up-right
    If you're familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

    There are two common kinds of side effects in React components: those that don't require cleanup, and those that do. Let's look at this distinction in more detail.

    hashtag
    Effects Without Cleanup

    Sometimes, we want to run some additional code after React has updated the DOM. Network requests, manual DOM mutations, and logging are common examples of effects that don't require a cleanup. We say that because we can run them and immediately forget about them. Let's compare how classes and Hooks let us express such side effects.

    hashtag
    Example Using Classes

    In React class components, the render method itself shouldn't cause side effects. It would be too early -- we typically want to perform our effects after React has updated the DOM.

    This is why in React classes, we put side effects into componentDidMount and componentDidUpdate. Coming back to our example, here is a React counter class component that updates the document title right after React makes changes to the DOM:

    Note how we have to duplicate the code between these two lifecycle methods in class.

    This is because in many cases we want to perform the same side effect regardless of whether the component just mounted, or if it has been updated. Conceptually, we want it to happen after every render -- but React class components don't have a method like this. We could extract a separate method but we would still have to call it in two places.

    Now let's see how we can do the same with the useEffect Hook.

    hashtag
    Example Using Hooks

    We've already seen this example at the top of this page, but let's take a closer look at it:

    What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we'll refer to it as our "effect"), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

    Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don't need a special API to read it -- it's already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.

    Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize thisarrow-up-right.) Instead of thinking in terms of "mounting" and "updating", you might find it easier to think that effects happen "after render". React guarantees the DOM has been updated by the time it runs the effects.

    hashtag
    Detailed Explanation

    Now that we know more about effects, these lines should make sense:

    We declare the count state variable, and then we tell React we need to use an effect. We pass a function to the useEffect Hook. This function we pass is our effect. Inside our effect, we set the document title using the document.title browser API. We can read the latest count inside the effect because it's in the scope of our function. When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM. This happens for every render, including the first one.

    Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale. Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes the effects behave more like a part of the render result -- each effect "belongs" to a particular render. We will see more clearly why this is useful later on this pagearrow-up-right.

    Tip

    Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don't block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don't need to happen synchronously. In the uncommon cases where they do (such as measuring the layout), there is a separate useLayoutEffect Hook with an API identical to useEffect.

    hashtag
    Effects with Cleanup

    Earlier, we looked at how to express side effects that don't require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don't introduce a memory leak! Let's compare how we can do it with classes and with Hooks.

    hashtag
    Example Using Classes

    In a React class, you would typically set up a subscription in componentDidMount, and clean it up in componentWillUnmount. For example, let's say we have a ChatAPI module that lets us subscribe to a friend's online status. Here's how we might subscribe and display that status using a class:

    Notice how componentDidMount and componentWillUnmount need to mirror each other. Lifecycle methods force us to split this logic even though conceptually code in both of them is related to the same effect.

    Note

    Eagle-eyed readers may notice that this example also needs a componentDidUpdate method to be fully correct. We'll ignore this for now but will come back to it in a later sectionarrow-up-right of this page.

    hashtag
    Example Using Hooks

    Let's see how we could write this component with Hooks.

    You might be thinking that we'd need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up:

    Why did we return a function from our effect? This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. They're part of the same effect!

    When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We'll discuss why this helps avoid bugsarrow-up-right and how to opt out of this behavior in case it creates performance issuesarrow-up-right later below.

    Note

    We don't have to return a named function from the effect. We called it cleanup here to clarify its purpose, but you could return an arrow function or call it something different.

    hashtag
    Recap

    We've learned that useEffect lets us express different kinds of side effects after a component renders. Some effects might require cleanup so they return a function:

    Other effects might not have a cleanup phase, and don't return anything.

    The Effect Hook unifies both use cases with a single API.


    If you feel like you have a decent grasp on how the Effect Hook works, or if you feel overwhelmed, you can jump to the next page about Rules of Hooks now.


    hashtag
    Tips for Using Effects

    We'll continue this page with an in-depth look at some aspects of useEffect that experienced React users will likely be curious about. Don't feel obligated to dig into them now. You can always come back to this page to learn more details about the Effect Hook.

    hashtag
    Tip: Use Multiple Effects to Separate Concerns

    One of the problems we outlined in the Motivation for Hooks is that class lifecycle methods often contain unrelated logic, but related logic gets broken up into several methods. Here is a component that combines the counter and the friend status indicator logic from the previous examples:

    Note how the logic that sets document.title is split between componentDidMount and componentDidUpdate. The subscription logic is also spread between componentDidMount and componentWillUnmount. And componentDidMount contains code for both tasks.

    So, how can Hooks solve this problem? Just like you can use the State Hook more than once, you can also use several effects. This lets us separate unrelated logic into different effects:

    Hooks let us split the code based on what it is doing rather than a lifecycle method name. React will apply every effect used by the component, in the order they were specified.

    hashtag
    Explanation: Why Effects Run on Each Update

    If you're used to classes, you might be wondering why the effect cleanup phase happens after every re-render, and not just once during unmounting. Let's look at a practical example to see why this design helps us create components with fewer bugs.

    Earlier on this pagearrow-up-right, we introduced an example FriendStatus component that displays whether a friend is online or not. Our class reads friend.id from this.props, subscribes to the friend status after the component mounts, and unsubscribes during unmounting:

    But what happens if the friend prop changes while the component is on the screen? Our component would continue displaying the online status of a different friend. This is a bug. We would also cause a memory leak or crash when unmounting since the unsubscribe call would use the wrong friend ID.

    In a class component, we would need to add componentDidUpdate to handle this case:

    Forgetting to handle componentDidUpdate properly is a common source of bugs in React applications.

    Now consider the version of this component that uses Hooks:

    It doesn't suffer from this bug. (But we also didn't make any changes to it.)

    There is no special code for handling updates because useEffect handles them by default. It cleans up the previous effects before applying the next effects. To illustrate this, here is a sequence of subscribe and unsubscribe calls that this component could produce over time:

    This behavior ensures consistency by default and prevents bugs that are common in class components due to missing update logic.

    hashtag
    Tip: Optimizing Performance by Skipping Effects

    In some cases, cleaning up or applying the effect after every render might create a performance problem. In class components, we can solve this by writing an extra comparison with prevProps or prevState inside componentDidUpdate:

    This requirement is common enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven't changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

    In the example above, we pass [count] as the second argument. What does this mean? If the count is 5, and then our component re-renders with count still equal to 5, React will compare [5] from the previous render and [5] from the next render. Because all items in the array are the same (5 === 5), React would skip the effect. That's our optimization.

    When we render with count updated to 6, React will compare the items in the [5] array from the previous render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6. If there are multiple items in the array, React will re-run the effect even if just one of them is different.

    This also works for effects that have a cleanup phase:

    In the future, the second argument might get added automatically by a build-time transformation.

    Note

    If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Learn more about how to deal with functions and what to do when the array changes too often.

    If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn't depend on any values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the dependencies array always works.

    If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don't forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.

    We recommend using the rule as part of our package. It warns when dependencies are specified incorrectly and suggests a fix.

    hashtag
    Next Steps

    Congratulations! This was a long page, but hopefully by the end most of your questions about effects were answered. You've learned both the State Hook and the Effect Hook, and there is a lot you can do with both of them combined. They cover most of the use cases for classes -- and where they don't, you might find the additional Hooks helpful.

    We're also starting to see how Hooks solve problems outlined in Motivation. We've seen how effect cleanup avoids duplication in componentDidUpdate and componentWillUnmount, brings related code closer together, and helps us avoid bugs. We've also seen how we can separate effects by their purpose, which is something we couldn't do in classes at all.

    At this point you might be questioning how Hooks work. How can React know which useState call corresponds to which state variable between re-renders? How does React "match up" previous and next effects on every update? On the next page we will learn about the Rules of Hooks -- they're essential to making Hooks work.

    Portals

    Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

    The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.

    hashtag
    Usage

    Normally, when you return an element from a component's render method, it's mounted into the DOM as a child of the nearest parent node:

    However, sometimes it's useful to insert a child into a different location in the DOM:

    A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually "break out" of its container. For example, dialogs, hovercards, and tooltips.

    Note:

    When working with portals, remember that managing keyboard focus becomes very important.

    For modal dialogs, ensure that everyone can interact with them by following the .

    hashtag
    Event Bubbling Through Portals

    Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.

    This includes event bubbling. An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree. Assuming the following HTML structure:

    A Parent component in #app-root would be able to catch an uncaught, bubbling event from the sibling node #modal-root.

    Catching an event bubbling up from a portal in a parent component allows the development of more flexible abstractions that are not inherently reliant on portals. For example, if you render a <Modal /> component, the parent can capture its events regardless of whether it's implemented using portals.

    SAP Fieldglass Sign Inwww.fieldglass.netchevron-right
    https://mytime.duke-energy.commytime.duke-energy.comchevron-right
    My time
    My Appsaccount.activedirectory.windowsazure.comchevron-right
    <iframe title="Video: Why Join Power Manager"></iframe>
    <a href="/home/billing/credit-card-debit-card-or-electronic-check-payments">Card or eCheck Payments</a>
    <iframe title="Website Feedback panel"></iframe>
    <label for="usernameId">User Name</label> <input type="text" id="usernameId" autocomplete="username">
    <ol ...>
    <input autocomplete="postal-code" id="zip" name="ZipCode" type="text">
    <img alt="" width="100" height="100" src="...">
    <iframe title="Video: Application Information NC Solar Rebate Program"></iframe>
    <img src="stock1.jpg" alt="">
    <img src="stock1.jpg" alt="">
    <img alt="" src="...">
    <img alt="" src="https://desitecoredev92-cd.azureedge.net/_/media/images/svg/account/iconquestion.svg?h=100&la=en&w=100&rev=cff914c360f8411c80ba3ccfad6880f5">
    <img alt="" src=...">
    <img alt="" width="100" height="100" src="...">
    <a href="...">View Resources <span class="screenReader">for special assistance</span></a>
    <img alt="" width="1280" height="375" src=...">
    <img src="stock1.jpg" alt="">
    <img src="stock1.jpg" alt="">
    <img alt="" width="100" height="100" src="...">
    <img alt="" width="100" height="100" src="...">
    <svg aria-hidden="true" focusable="false">
    <span class="flex-grow">Asheville 2021 3rd Quarter SOC Monitoring Report (PDF)</span>
    <img src="stock1.jpg" alt="">
    <img src="..." alt="Close feedback form">
    <button aria-label="Close feedback form">
    <img src="..." alt="">
    <button class="slider-control-SI_dgOopUlfa9iTuAZ" aria-label="Feedback form"
    aria-expanded="false" class=">
    <svg role="img" aria-labelledby="checkmark" focusable="false">
    <title id="checkmark">Your information has been received.</title>
    <svg role="img" aria-labelledby="checkmark1">
    <title id="checkmark1">Complete</title>
    <input id="address" type="text" aria-invalid="true">
    <input id="email" type="email" aria-invalid="true" aria-describedby="email_error">
    <p id="email_error">Please enter a valid email address in the format name@example.com</p>
    <div role="dialog" aria-label="dlgHeading" class="py-12">
    <h2 id="dlgHeading">Where are you located in South Carolina?</h2>
    <a href="..." aria-label="View resources for special assistance">View Resources</a>
    aria-describedby="logo-desc">
    <desc id="logo-desc">Shaped like a shield with the text 'HTML' above and the numeral '5' prominent on the face of the shield.</desc>
    <svg version="1.1" aria-hidden="true">
    <svg version="1.1" aria-hidden="true"
    focusable="false">
    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // Similar to componentDidMount and componentDidUpdate:
      useEffect(() => {
        // Update the document title using the browser API
        document.title = `You clicked ${count} times`;
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
    
      componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
      }
    
      componentDidUpdate() {
        document.title = `You clicked ${this.state.count} times`;
      }
    
      render() {
        return (
          <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
              Click me
            </button>
          </div>
        );
      }
    }
    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    function Example() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    }
    class FriendStatus extends React.Component {
      constructor(props) {
        super(props);
        this.state = { isOnline: null };
        this.handleStatusChange = this.handleStatusChange.bind(this);
      }
    
      componentDidMount() {
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      componentWillUnmount() {
        ChatAPI.unsubscribeFromFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      handleStatusChange(status) {
        this.setState({
          isOnline: status.isOnline
        });
      }
    
      render() {
        if (this.state.isOnline === null) {
          return 'Loading...';
        }
        return this.state.isOnline ? 'Online' : 'Offline';
      }
    }
    import React, { useState, useEffect } from 'react';
    
    function FriendStatus(props) {
      const [isOnline, setIsOnline] = useState(null);
    
      useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
    
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        // Specify how to clean up after this effect:
        return function cleanup() {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
    }
    useEffect(() => {
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    });
    useEffect(() => {
      document.title = `You clicked ${count} times`;
    });
    class FriendStatusWithCounter extends React.Component {
      constructor(props) {
        super(props);
        this.state = { count: 0, isOnline: null };
        this.handleStatusChange = this.handleStatusChange.bind(this);
      }
    
      componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      componentDidUpdate() {
        document.title = `You clicked ${this.state.count} times`;
      }
    
      componentWillUnmount() {
        ChatAPI.unsubscribeFromFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      handleStatusChange(status) {
        this.setState({
          isOnline: status.isOnline
        });
      }
      // ...
    function FriendStatusWithCounter(props) {
      const [count, setCount] = useState(0);
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      const [isOnline, setIsOnline] = useState(null);
      useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
    
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
      // ...
    }
      componentDidMount() {
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      componentWillUnmount() {
        ChatAPI.unsubscribeFromFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
      componentDidMount() {
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      componentDidUpdate(prevProps) {
        // Unsubscribe from the previous friend.id
        ChatAPI.unsubscribeFromFriendStatus(
          prevProps.friend.id,
          this.handleStatusChange
        );
        // Subscribe to the next friend.id
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    
      componentWillUnmount() {
        ChatAPI.unsubscribeFromFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
    function FriendStatus(props) {
      // ...
      useEffect(() => {
        // ...
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    // Mount with { friend: { id: 100 } } props
    ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect
    
    // Update with { friend: { id: 200 } } props
    ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
    ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect
    
    // Update with { friend: { id: 300 } } props
    ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
    ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect
    
    // Unmount
    ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect
    componentDidUpdate(prevProps, prevState) {
      if (prevState.count !== this.state.count) {
        document.title = `You clicked ${this.state.count} times`;
      }
    }
    useEffect(() => {
      document.title = `You clicked ${count} times`;
    }, [count]); // Only re-run the effect if count changes
    useEffect(() => {
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    }, [props.friend.id]); // Only re-subscribe if props.friend.id changes
    ReactDOM.createPortal(child, container);

    .content-between

    align-content: space-between;

    .content-around

    align-content: space-around;

    .content-evenly

    align-content: space-evenly;

    .place-content-between

    place-content: space-between;

    .place-content-around

    place-content: space-around;

    .place-content-evenly

    place-content: space-evenly;

    .place-content-stretch

    place-content: stretch;

    .text-opacity-20

    --tw-text-opacity: 0.2;

    .text-opacity-25

    --tw-text-opacity: 0.25;

    .text-opacity-30

    --tw-text-opacity: 0.3;

    .text-opacity-40

    --tw-text-opacity: 0.4;

    .text-opacity-50

    --tw-text-opacity: 0.5;

    .text-opacity-60

    --tw-text-opacity: 0.6;

    .text-opacity-70

    --tw-text-opacity: 0.7;

    .text-opacity-75

    --tw-text-opacity: 0.75;

    .text-opacity-80

    --tw-text-opacity: 0.8;

    .text-opacity-90

    --tw-text-opacity: 0.9;

    .text-opacity-95

    --tw-text-opacity: 0.95;

    .text-opacity-100

    --tw-text-opacity: 1;

    .text-justify

    text-align: justify;

    .normal-case

    text-transform: none;

    exhaustive-depsarrow-up-right
    eslint-plugin-react-hooksarrow-up-right
    WAI-ARIA Modal Authoring Practicesarrow-up-right
    Try it on CodePenarrow-up-right
    Try it on CodePenarrow-up-right
    render() {
      // React mounts a new div and renders the children into it
      return (
        <div>
          {this.props.children}
        </div>
      );
    }
    render() {
      // React does *not* create a new div. It renders the children into `domNode`.
      // `domNode` is any valid DOM node, regardless of its location in the DOM.
      return ReactDOM.createPortal(
        this.props.children,
        domNode
      );
    }
    <html>
      <body>
        <div id="app-root"></div>
        <div id="modal-root"></div>
      </body>
    </html>
    // These two containers are siblings in the DOM
    const appRoot = document.getElementById('app-root');
    const modalRoot = document.getElementById('modal-root');
    
    class Modal extends React.Component {
      constructor(props) {
        super(props);
        this.el = document.createElement('div');
      }
    
      componentDidMount() {
        // The portal element is inserted in the DOM tree after
        // the Modal's children are mounted, meaning that children
        // will be mounted on a detached DOM node. If a child
        // component requires to be attached to the DOM tree
        // immediately when mounted, for example to measure a
        // DOM node, or uses 'autoFocus' in a descendant, add
        // state to Modal and only render the children when Modal
        // is inserted in the DOM tree.
        modalRoot.appendChild(this.el);
      }
    
      componentWillUnmount() {
        modalRoot.removeChild(this.el);
      }
    
      render() {
        return ReactDOM.createPortal(
          this.props.children,
          this.el
        );
      }
    }
    
    class Parent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {clicks: 0};
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        // This will fire when the button in Child is clicked,
        // updating Parent's state, even though button
        // is not direct descendant in the DOM.
        this.setState(state => ({
          clicks: state.clicks + 1
        }));
      }
    
      render() {
        return (
          <div onClick={this.handleClick}>
            <p>Number of clicks: {this.state.clicks}</p>
            <p>
              Open up the browser DevTools
              to observe that the button
              is not a child of the div
              with the onClick handler.
            </p>
            <Modal>
              <Child />
            </Modal>
          </div>
        );
      }
    }
    
    function Child() {
      // The click event on this button will bubble up to parent,
      // because there is no 'onClick' attribute defined
      return (
        <div className="modal">
          <button>Click</button>
        </div>
      );
    }
    
    ReactDOM.render(<Parent />, appRoot);
  • to the element with
    role="dialog"
    .
  • Set focus to the element with role="dialog" or to the <nav> within the dialog. Apply tabindex="-1" to the element serving as the focus target. This is further discussed in a separate 2.4.3 assertion.

  • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

  • Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply aria-hidden="true" to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the aria-hidden attribute from page content when the dialog is closed.

  • The side navigation currently has a close button. It is suggest to add functionality where pressing ESC key dismisses the dialog and returns focus to the hamburger menu button.

  • </html>

    There is no announcement for screen reader users when the volume slider is adjusted.

  • There are parsing errors, including but not limited to stray end tags and unclosed elements.

  • When text spacing is increased, "Watch Later" becomes "Watch La," which is more difficult to understand.

  • Use third party embedding systems that provide control over accessibility.
  • Manually or automatically check and block inaccessible third party content.

  • Provide accessible alternative formats for content that can't be remediated.

  • Tell users when third party content may not be accessible, and how they can access the content.

  • Use a <caption> element to label the table. For ARIA based tables, use an ARIA labeling method such as aria-labelledby.

  • Use a <tr> element for each row, or the row role for rows in ARIA based tables.

  • Use the <th> element for row and column headers. To indicate what dimension the header applies to, the scope=col and scope=row attributes for row headers are implied by user agents assistive technology so are generally not needed. For ARIA based tables, use the columnheader and rowheader roles.

  • Use the <td> element for each cell (or an element with a cell role for ARIA based tables, gridcell if using the grid role).

  • Use the <th> element for row and column headers. To indicate what dimension the header applies to, the scope=col and scope=row attributes for row headers are implied by user agents assistive technology so are generally not needed. For ARIA based tables, use the columnheader and rowheader roles.
  • Use the <td> element for each cell (or an element with a cell role for ARIA based tables, gridcell if using the grid role).

  • <td>412-212-5400</td>
    <td>Pittsburgh</td>
    </tr>
    <tr>
    <td>2.</td>
    <th>Clive Lloyd</th>
    <td>410-306-1420</td>
    <td>410-306-5400</td>
    <td>Baltimore</td>
    </tr>
    <tr>
    <td>3.</td>
    <th>Gordon Greenidge</th>
    <td>281-564-6720</td>
    <td>281-511-6600</td>
    <td>Houston</td>
    </tr>
    </table>
    Add JavaScript event handlers to accept SPACE (in addition to ENTER) to activate the button.
    Add aria-expanded attribute to the control and toggle its value to reflect whether the content it controls is collapsed or expanded.
    Remove the placeholder text.
    bookmarklet

    Ensure all headings provide more context about what the alert is about. For example, "System maintenance September 25th and 26th" rather than "Test Alert".

  • While not a violation, we recommend changing the alt value of the image from "icon alert" to "alert".

  • Add the role dialog to the search panel container.
  • When the search dialog opens, focus is set to the search input. This is good behavior. No need to change that.

  • Ensure that pressing ESCAPE on the keyboard dismisses the dialog.

  • When the dialog is closed, set focus to the element which triggered the dialog to open. In this case, set focus to the "Search" button.

  • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

  • Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply aria-hidden="true" to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the aria-hidden attribute from page content when the dialog is closed.

  • Add the role dialog to the containing element.
  • Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply aria-hidden="true" to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the aria-hidden attribute from page content when the dialog is closed.

  • Programmatically shift focus back to the "watch video" control when the dialog is closed.

  • Remove role="button" from all elements that are not buttons.

  • Remove aria-hidden from the dialog.

  • 4.1.1 Parsing

    For each slide container, use role="group" and aria-roledescription="slide".

    attribute can be applied to the element with
    role="dialog"
    to provide an accessible name.
  • Ensure that the content obscured by the modal dialog is properly hidden from assistive technologies. Apply aria-hidden="true" to the inactive page content behind the dialog. This hides it from the reading order as perceived by assistive technology, while allowing it to remain slightly visible behind the dialog. Be sure to remove the aria-hidden attribute from page content when the dialog is closed.

  • When the dialog is launched, set focus to the dialog container or a descendant within the dialog.

  • Ensure that keyboard focus is constrained to the modal dialog until it is dismissed.

  • Landmarksarrow-up-right
    Support for landmarksarrow-up-right
    Check for WCAG 2.1 parsingarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    W3C Document conformance requirements for use of ARIA attributes in HTMLarrow-up-right
    W3C Document conformance requirements for use of ARIA attributes in HTMLarrow-up-right
    A short note on the use of aria-labelarrow-up-right
    General Principles of Landmark Designarrow-up-right
    Easy content organisation with HTML5arrow-up-right
    Landmarks browser extensionarrow-up-right
    Text alternatives for imagesarrow-up-right
    WAI-ARIA Authoring Practices - Menu Buttonarrow-up-right
    WCAG Technique: Providing descriptive labelsarrow-up-right
    ARIA Authoring Practices Modal Dialog Examplearrow-up-right
    Accessible Modal Dialogarrow-up-right
    a11y-dialog widgetarrow-up-right
    W3C Document conformance requirements for use of ARIA attributes in HTMLarrow-up-right
    W3C Document conformance requirements for use of ARIA attributes in HTMLarrow-up-right
    media queriesarrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    Understanding Conformancearrow-up-right
    Inaccessible Third-party Content and Code — Why it is Important, and How to Address itarrow-up-right
    WCAG Technique: Providing descriptive labelsarrow-up-right
    Check serialized DOM of current page (DOM Checking)arrow-up-right
    Check for WCAG 2.1 parsing compliancearrow-up-right
    W3C validatorarrow-up-right
    Using the HTML title attributearrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Understanding Success Criterion 2.4.4: Link Purpose (In Context)arrow-up-right
    Link Text and Appearancearrow-up-right
    Understanding Focus Orderarrow-up-right
    Responsive web design basicsarrow-up-right
    Understanding Resize textarrow-up-right
    What is an accessible name?arrow-up-right
    button elementarrow-up-right
    button design patternarrow-up-right
    What is an accessible name?arrow-up-right
    Understanding 2.5.3 Label in Namearrow-up-right
    How to Meet 2.5.3 Label in Namearrow-up-right
    ARIA Authoring Practices Disclosure (Show/Hide) Design Patternarrow-up-right
    Disclosure Widgetsarrow-up-right
    Using description listsarrow-up-right
    Using ol, ul and dl for lists or groups of linksarrow-up-right
    Using description listsarrow-up-right
    HTML5 Accessibility Chops: the placeholder attributearrow-up-right
    Describing aria-describedbyarrow-up-right
    aria-invalid (state)arrow-up-right
    Using aria-invalid to indicate an error fieldarrow-up-right
    Required attribute requirementsarrow-up-right
    Forms Modearrow-up-right
    media queriesarrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    Check serialized DOM of current page (DOM Checking)arrow-up-right
    Check for WCAG 2.1 parsing compliancearrow-up-right
    W3C validatorarrow-up-right
    WCAG Parsing Criterion is a PITAarrow-up-right
    Check serialized DOM of current page (DOM Checking)arrow-up-right
    W3C validatorarrow-up-right
    Using ol, ul and dl for lists or groups of linksarrow-up-right
    Understanding Success Criterion 2.4.3: Focus Orderarrow-up-right
    Date Picker Design Pattern (draft)arrow-up-right
    Maybe You Don't Need a Date Pickerarrow-up-right
    Describing Techniquesarrow-up-right
    Labeling Controlsarrow-up-right
    media queriesarrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    W3C Headings tutorialarrow-up-right
    HTML heading elementsarrow-up-right
    WCAG Technique: Providing descriptive labelsarrow-up-right
    Accordion (Sections With Show/Hide Functionality) Design Patternarrow-up-right
    Accordion examplearrow-up-right
    Understanding Success Criterion 3.3.3: Error Suggestionarrow-up-right
    HTML5 Accessibility Chops: the placeholder attributearrow-up-right
    ARIA Authoring Practices Modal Dialog Examplearrow-up-right
    Accessible Modal Dialogarrow-up-right
    a11y-dialog widgetarrow-up-right
    WCAG Technique: Providing descriptive labelsarrow-up-right
    Designing Custom Focus Indicatorsarrow-up-right
    Indicating focus to improve accessibilityarrow-up-right
    media queriesarrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Using media queries to un-fixing sticky headers/footersarrow-up-right
    Links, Buttons, Submits, and Divsarrow-up-right
    ARIA Authoring Practices Modal Dialog Examplearrow-up-right
    Accessible Modal Dialogarrow-up-right
    a11y-dialog widgetarrow-up-right
    WCAG Technique: Providing descriptive labelsarrow-up-right
    Link Targets and 3.2.5arrow-up-right
    media queriesarrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Using media queries to un-fixing sticky headers/footersarrow-up-right
    Using ol, ul and dl for lists or groups of linksarrow-up-right
    Using description listsarrow-up-right
    media queriesarrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Using media queries to un-fixing sticky headers/footersarrow-up-right
    WCAG 2.1: Understanding Success Criterion 1.4.12: Text Spacingarrow-up-right
    TPGi blog: Short note on getting spaced out with SC 1.4.12 Text Spacingarrow-up-right
    media queriesarrow-up-right
    Preventing focus being obscured by sticky content (CodePen)arrow-up-right
    Responsive designarrow-up-right
    Understanding Success Criterion 1.4.10: Reflowarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    W3C Headings tutorialarrow-up-right
    HTML heading elementsarrow-up-right
    Carousel Design Patternarrow-up-right
    Example: Carousel with buttonsarrow-up-right
    Example: Carousel with tablistarrow-up-right
    Link Targets and 3.2.5arrow-up-right
    WAI Tutorials: Grouping Controlsarrow-up-right
    TPGi: Fieldsets, Legends and Screen Readersarrow-up-right
    4.1.2arrow-up-right
    Modal Dialog Example - WAI-ARIA Authoring Practices 1.2arrow-up-right
    ARIA Authoring Practices Modal Dialog Examplearrow-up-right
    Accessible Modal Dialogarrow-up-right
    a11y-dialog widgetarrow-up-right
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: High
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Low
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Highest
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: High
    Issue Type: Epic
    Priority: Medium
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Lowest
    Issue Type: Story
    Priority: Low
    Issue Type: Story
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: High
    Issue Type: Story
    Priority: High
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Uploaded image for project: 'DXT Ninja Turtles'
    Sub-task - The sub-task of the issue
    BGuner
    BSowa
    <nav aria-label="Submenu"></nav>
    <img alt="" src="...">
    <button aria-label="Navigation menu"
    aria-haspopup="true"_
    <button aria-label="Open full site navigation"
    aria-haspopup="true"_>
    <iframe name="..." hidden src="..." title="No user content" ...></iframe>
    <iframe name="..." class="hidden" src="..." title="No user content" ...></iframe>
    <div role="table" aria-labelledby="Caption">
    <div id="Caption">Contact Information</div>
    <div role="row">
    <div role="cell">
    <div role="columnheader">Name</div>
    <div role="columnheader">Phone#</div>
    <div role="columnheader">Fax#</div>
    <div role="columnheader">City</div>
    </div> <div role="row">
    <div role="cell">1.</div>
    <div role="rowheader">Joel Garner</div>
    <div role="cell">412-212-5421</div>
    <div role="cell">412-212-5400</div>
    <div role="cell">Pittsburgh</div>
    </div> <div role="row">
    <div role="cell">2.</div>
    <div role="rowheader">Clive Lloyd</div>
    <div role="cell">410-306-1420</div>
    <div role="cell">410-306-5400</div>
    <div role="cell">Baltimore</div>
    <div role="row">
    <div role="cell">3.</div>
    <div role="rowheader">Gordon Greenidge</div>
    <div role="cell">281-564-6720</div>
    <div role="cell">281-511-6600</div>
    <div role="cell">Houston</div>
    <img alt="Parental Leave - Chris Bethany Mosteller video">
    <button aria-expanded="true">Plan Disclaimer</button>
    <iframe name="..." hidden src="..." title="No user content" ...></iframe>
    <iframe name="..." class="hidden" src="..." title="No user content" ...></iframe>
    <button
    aria-label="Close website feedback form">
    <img src="..." alt="">
    </button>
    <button
    aria-expanded="false">
    </button>
    <button aria-label="Feedback" aria-expanded="false">
    </button>
    <textarea id="Comments" aria-describedby="char-limit"></textarea>
    <p id="char-limit">Please note: Comments cannot exceed 250 characters</p>
    <input type="text" id="employeeName" aria-required="true">
    <input type="text" id="employeeName" aria-required="true">
    <input id="address" type="text" aria-invalid="true">
    <input id="email" type="email" aria-invalid="true" aria-describedby="email_error">
    <p id="email_error">Please enter a valid email address in the format name@example.com</p>
    <input type="password" id="passwordId" autocomplete="current-password">
    </ol>
    a:hover, a:focus { / existing hover styles */ }
    <h2 id="group-label">Select your location</h2>
    <ul aria-labelledby="grouplabel">
    <div aria-live="polite" role="status" aria-atomic="true">
    Landmarksarrow-up-right
    Support for landmarksarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    Check for WCAG 2.1 parsingarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    Under-Engineered Responsive Tablesarrow-up-right
    The current state of modal dialog accessibilityarrow-up-right
    Logo
    Logo
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: High
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Epic
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: High
    Priority: Medium
    Priority: Medium
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Priority: High
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Issue Type: Story
    Priority: Medium
    Duke Energy JIRA
    36546.png
    User profile for Guner, Bryan
    DXT Ninja Turtles
    BGuner
    Screen Shot 2022-03-28 at 4.36.23 PM.png
    Issue Type: Sub-task
    Logo
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Bug
    Issue Type: Bug
    Issue Type: Bug
    Assignee: Guest, James
    Issue Type: Bug
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Greufe, Chris
    Issue Type: Sub-task
    Assignee: Holly, Joshua R
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Holly, Joshua R
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Pope, Spencer O
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Sharo, John
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Evanoff, Matthew
    Issue Type: Task
    Assignee: Pope, Spencer O
    Issue Type: Task
    Assignee: Guest, James
    Assignee: Guner, Bryan
    Assignee: Pope, Spencer O
    Assignee: Rogers, Stephen Michael
    Issue Type: Bug
    Assignee: Crane, Donald
    Issue Type: Sub-task
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Guner, Bryan
    Issue Type: Sub-task
    Issue Type: Bug
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Slavik, Nicolas [X]
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Issue Type: Sub-task
    Assignee: Parkhouse, Russell
    Issue Type: Bug
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Rogers, Stephen Michael
    Issue Type: Sub-task
    Assignee: Guest, James
    Assignee: Guner, Bryan
    Assignee: Holly, Joshua R
    Assignee: Holly, Joshua R
    Issue Type: Sub-task
    Assignee: Williams, Mike
    Issue Type: Sub-task
    Assignee: Pope, Spencer O
    Assignee: Rogers, Stephen Michael
    Assignee: Guner, Bryan
    Issue Type: Bug
    Assignee: Evanoff, Matthew
    Assignee: Babu, Sarath
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Evanoff, Matthew
    Assignee: Holly, Joshua R
    Issue Type: Bug
    Assignee: Guest, James
    Assignee: Evanoff, Matthew
    Assignee: Williams, Mike
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Sub-task
    Assignee: Williams, Mike
    Issue Type: Bug
    Assignee: Guner, Bryan
    Assignee: Macias, Marcie
    Assignee: Williams, Mike
    Issue Type: Sub-task
    Assignee: Rubaiyat, Mahjabin
    Issue Type: Bug
    Assignee: Holly, Joshua R
    Assignee: Crane, Donald
    https://jiraprod.duke-energy.com/browse/DNT-2930jiraprod.duke-energy.comchevron-right
    https://electron.duke-energy.com/components/hero/electron.duke-energy.comchevron-right
    https://local.duke-energy.com:3000/home/start-stop-move/stoplocal.duke-energy.comchevron-right
    DNT-2860 Go Green Calculator | DUKEbryan-guner.gitbook.iochevron-right
    https://local.duke-energy.com:3000/home-services/gas-line-repair/enrolllocal.duke-energy.comchevron-right
    https://local.duke-energy.com:3000/home-services/gas-line-repair/enrolllocal.duke-energy.comchevron-right
    https://local.duke-energy.com:3000/home-services/gas-line-repair/enrollarrow-up-right
    Five Things You (Probably) Didn't Know About Testing Librarypolvara.mechevron-right
    Logo
    Logo