anchor-is-valid

Try in Playground
jsx-a11y

oscar143

Best PracticeWarning

0

No tags

No CWE or CVE

jsx-a11y/anchor-is-valid

The HTML <a> element, with a valid href attribute, is formally defined as representing a hyperlink. That is, a link between one HTML document and another, or between one location inside an HTML document and another location inside the same document.

In fact, the interactive, underlined <a> element has become so synonymous with web navigation that this expectation has become entrenched inside browsers, assistive technologies such as screen readers and in how people generally expect the internet to behave. In short, anchors should navigate.

The use of JavaScript frameworks and libraries, like React, has made it very easy to add or subtract functionality from the standard HTML elements. This has led to anchors often being used in applications based on how they look and function instead of what they represent.

Whilst it is possible, for example, to turn the <a> element into a fully functional <button> element with ARIA, the native user agent implementations of HTML elements are to be preferred over custom ARIA solutions.

Accessibility guidelines

  • WCAG 2.1.1

Resources

Ast Rule: html element


anchor-is-valid

How to write a rule
const TYPES = {
  string: getPropStringValue,
  sequence: getPropSequenceValue,
  array: getPropArrayValue,
  object: getPropObjectValue,
  object_element: getPropObjectValue,
  functiondefinition: () => {},
};

function getTag(node) {
  if (node && node.tag) {
    return node.tag.value;
  }
}

function getProp(attributes = [], prop = "") {
  if (!prop) return;

  return attributes.find((attribute) => {
    if (attribute && attribute.name && attribute.name.value) {
      return attribute.name.value === prop;
    }
  });
}

function getPropStringValue(value) {
  if (value.value === "true") {
    return true;
  }

  if (value.value === "false") {
    return false;
  }

  if (value.value === "undefined") {
    return undefined;
  }

  if (value.value === "null") {
    return null;
  }

  return value.value.replace(/^[\"\`]/g, "").replace(/[\"\`]$/g, "");
}

function getPropArrayValue(value) {
  const array = [];
  const arrayElements = value.elements;

  if (arrayElements.length) {
    for (const element of arrayElements) {
      array.push(TYPES[element.astType](element));
    }
  }

  return array;
}

function getPropObjectValue(value) {
  const object = {};
  const objectElements = value.elements;

  if (objectElements.length) {
    for (const element of objectElements) {
      object[element.name.value] = TYPES[element.value.astType](element.value);
    }
  }

  return object;
}

function getPropSequenceValue(value) {
  console.log(JSON.stringify(value));
  if (value.elements.length) {
    const element = value.elements[0];

    if (element) {
      return TYPES[element.astType](element);
    }
  }
}

function extractValue(attribute, extractor) {
  if (attribute) {
    // Null valued attributes imply truthiness.
    // For example: <div aria-hidden />
    if (attribute.value === null) return true;

    return extractor(attribute.value);
  }
}

function getValue(value) {
  console.log(value.astType);
  return TYPES[value.astType](value);
}

function getPropValue(attribute) {
  return extractValue(attribute, getValue);
}

function debugObject(object) {
  for (const property in object) {
    console.log(`${property}: ${JSON.stringify(object[property])}`);
  }
}

function propHasValue(attribute) {
  const value = getPropValue(attribute);

  if (typeof value !== "boolean") {
    return !!value;
  }

  return false;
}

function visit(node) {
  console.log(node);
  if (getTag(node) === "a") {
    const href = getPropValue(getProp(node.attributes, "href"));
    const isHash = href === "#";
    const isScript = href?.startsWith("javascript:");
    const onClick = getProp(node.attributes, "onClick");

    if (onClick && (isHash || isScript || !href)) {
      const error = buildError(
        node.start.line,
        node.start.col,
        node.end.line,
        node.end.col,
        "Anchor used as a button. Anchors are primarily expected to navigate. Use the button element instead.",
        "INFO",
        "BEST_PRACTICES"
      );

      addError(error);

      return;
    }

    if (isScript || isHash) {
      const error = buildError(
        node.start.line,
        node.start.col,
        node.end.line,
        node.end.col,
        "The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value. If you cannot provide a valid href, but still need the element to resemble a link, use a button and change it with appropriate styles.",
        "INFO",
        "BEST_PRACTICES"
      );

      addError(error);

      return;
    }

    if (!href) {
      const error = buildError(
        node.start.line,
        node.start.col,
        node.end.line,
        node.end.col,
        "The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles.",
        "INFO",
        "BEST_PRACTICES"
      );

      addError(error);
    }
  }
}

bad.jsx

Expected test result: has error

troublesome anchor tags

// use a button instead
<a onClick={foo} />
<a href="#" onClick={foo} />
<a href={"#"} onClick={foo} />
<a href={`#`} onClick={foo} />
<a href="javascript:void(0)" onClick={foo} />
<a href={"javascript:void(0)"} onClick={foo} />
<a href={`javascript:void(0)`} onClick={foo} />

// use a valid link
<a href="#" />
<a href={"#"} />
<a href={`#`} />
<a href="javascript:void(0)" />
<a href={"javascript:void(0)"} />
<a href={`javascript:void(0)`} />

// missing href
<a />
<a href={undefined} />
<a href={null} />

good.jsx

Expected test result: no error

valid anchor tags

<a href="https://github.com" />
<a href="#section" />
<a href="foo" />
<a href="/foo/bar" />
<a href={someValidPath} />
<a href="https://github.com" onClick={foo} />
<a href="#section" onClick={foo} />
<a href="foo" onClick={foo} />
<a href="/foo/bar" onClick={foo} />
<a href={someValidPath} onClick={foo} />
Add comment

Log in to add a comment


    Be the first one to leave a comment!

Codiga Logo
Codiga Hub
  • Rulesets
  • Playground
  • Snippets
  • Cookbooks
soc-2 icon

We are SOC-2 Compliance Certified

G2 high performer medal

Codiga – All rights reserved 2022.