ESLint Plugin Rules

  1. no-deprecated-props: a prop the design system has marked @deprecated.
  2. no-restricted-eds-imports: a deprecated entry point, or an icon imported from eds-core.
  3. require-icon-button-aria-label: an IconButton with no accessible name.
  4. no-deprecated-components: an import of a deprecated export.
  5. prefer-eds-components: a raw HTML element the design system has a component for.

1. no-deprecated-props​

Flags a prop that @adaptavant/eds-core marks @deprecated. The message quotes the annotation from the design system source, so it always names the current replacement.

Two kinds of deprecation report here. A prop replaced by a new API:

// Incorrect
<Avatar imageSrc={url} />
// ESLint: `imageSrc` on `Avatar` is deprecated. Use `AvatarImage` instead.

// Correct
<Avatar><AvatarImage src={url} /></Avatar>

And a style prop replaced by a Tailwind utility class:

// Incorrect
<Text tone="primary" textAlign="center">Hi</Text>
// ESLint: `tone` on `Text` is deprecated. please use text color utility classes instead.
// ESLint: `textAlign` on `Text` is deprecated. please use text-align utility classes instead.

// Correct
<Text className="text-primary text-center">Hi</Text>

Heading accepts the style props through Pick<TextProps, …>, so it is covered too.

If the styling migration is noisy for you mid upgrade, turn the whole rule down to warn rather than disabling it.

Limitations​

The rule only sees a deprecated prop written directly on a component it can trace back to an @adaptavant/eds-core import. Three cases slip past it.

A prop passed through a spread, because the contents of the object are not knowable without type information:

const props = { errorMessage: 'Required' };
<Field {...props} />   // not reported

A component copied into another variable, because the rule follows the name the import introduced:

import { Field } from '@adaptavant/eds-core';

const MyField = Field;
<MyField errorMessage="Required" />   // not reported

A prop passed to your own wrapper, which forwards it on. The rule sees the prop on FormField, which is yours rather than an eds-core import, and inside the wrapper it is a spread:

function FormField(props) {
  return <Field {...props} />;
}

<FormField errorMessage="Required" />   // not reported

In each case the prop is still deprecated. The rule stays silent rather than guess.

2. no-restricted-eds-imports​

Flags an import that resolves through the wrong entry point. This rule is fixable with --fix.

Deprecated entry points​

@adaptavant/eds-core/experimental is going away. Everything it exports is available from the package root.

// Incorrect
import { Dialog } from '@adaptavant/eds-core/experimental';
// ESLint: '@adaptavant/eds-core/experimental' is deprecated. The `/experimental`
// entry point is going away. Import from `@adaptavant/eds-core` instead.

// Correct
import { Dialog } from '@adaptavant/eds-core';

Icons and symbols​

@adaptavant/eds-core re-exports every icon and symbol from @adaptavant/eds-icons. Import them from their own entry point instead, so icon usage can be upgraded independently of the core version:

  • icons from @adaptavant/eds-icons/icons
  • symbols from @adaptavant/eds-icons/symbols
// Incorrect
import { Box, ShareIcon, AmexColorIcon } from '@adaptavant/eds-core';
// ESLint: Import `ShareIcon` from '@adaptavant/eds-icons/icons', not '@adaptavant/eds-core'.
// ESLint: Import `AmexColorIcon` from '@adaptavant/eds-icons/symbols', not '@adaptavant/eds-core'.

// Correct
import { Box } from '@adaptavant/eds-core';
import { ShareIcon } from '@adaptavant/eds-icons/icons';
import { AmexColorIcon } from '@adaptavant/eds-icons/symbols';

Which entry point a name belongs to is generated from the design system source, not guessed from its spelling. Symbols end in Icon too: AmexIcon is an icon and AmexColorIcon is a symbol. For the same reason AvatarIcon and CalDynamicIcon, which are genuine eds-core components, are never reported.

What the autofix does​

Most imports mix icons with components in one statement, so the fix is usually a split rather than a change of path. The icon specifiers are removed from the original import, merged into an import of the target entry point if the file already has one, and the original statement is deleted only if nothing is left in it. An import holding both an icon and a symbol splits three ways.

The fix is withheld, and the violation reported on its own, when either statement has a default or namespace import such as import EDS, { ShareIcon } from '@adaptavant/eds-core', or when it is type only. Rebuilding such a statement as a plain named import would change what it binds.

Run your formatter after --fix to restore your own import formatting.

3. require-icon-button-aria-label​

Flags an IconButton with no aria-label, or one whose accessible name is empty. IconButton renders no visible text, so the label is the only name a screen reader has.

An empty name means any of: "", a whitespace only string, {''} or the valueless aria-label shorthand.

// Incorrect
<IconButton icon={CloseIcon} />
// ESLint: IconButton must have an aria-label prop for accessibility.

<IconButton icon={CloseIcon} aria-label="" />
// ESLint: IconButton aria-label must not be empty — it is the only accessible
// name a screen reader has.

// Correct
<IconButton icon={CloseIcon} aria-label="Close" />
<IconButton icon={CloseIcon} aria-label={t('close')} />

A dynamic value such as aria-label={t('close')} is never reported as empty, since the rule only judges strings it can read without running the code.

An element carrying a spread, such as <IconButton icon={X} {...buttonProps} />, is not reported for a missing label, because the spread may supply one.

Limitations​

A namespace import is not tracked. import * as eds from '@adaptavant/eds-core' followed by <eds.IconButton /> produces a member expression the rule does not resolve, so it reports nothing. The same applies to no-deprecated-props and no-deprecated-components.

4. no-deprecated-components​

Flags an import of a deprecated @adaptavant/eds-core export and names its replacement. Deprecated types and helper functions are covered as well as components.

The rule reports at the import rather than at the JSX site, so a component handed to something else as a value is caught too:

// Incorrect
import { ShareModal } from '@adaptavant/eds-core';
// ESLint: `ShareModal` is deprecated. This component is deprecated and will be
// removed in future versions. Instead use it from `@adaptavant/share-modal`

const dialogs = { share: ShareModal };  // also covered, via the import above

// Correct
import { ShareModal } from '@adaptavant/share-modal';

There is no autofix, because the replacement usually lives in a package your app does not depend on yet.

5. prefer-eds-components​

Flags a raw HTML element where the design system has a component that replaces it. Elements it has nothing for are never reported.

Some elements have more than one equivalent, so the message lists them all. A <button> is a Button or an IconButton depending on whether it has visible label text.

HTML elementDesign system component
<button><Button> or <IconButton>
<a><TextLink>, <ButtonLink> or <IconLink>
<select><SelectMenu>
<textarea><Textarea>
<img><Image>
<label><Label>
<fieldset><Fieldset>
<p><Text>
<h1> to <h6><Heading>
<progress><Progress>
<input>depends on type, below

<input> by type​

typeDesign system component
text, email, password, tel, url, or omitted<TextInput>
search<SearchInput>
checkbox<Checkbox> or <Toggle>
radio<Radio>
range<Slider>
button, submit, reset<Button>

Any other type reports nothing, including hidden, file, number, date, datetime-local, month, week, time, color and image. A non literal type such as <input type={kind} /> is also left alone.

Not covered​

Generic containers: div, span, li, ul, ol, section, article, main, header, footer, nav and aside. A <div> has no design system behaviour to miss out on, and replacing <nav>, <main>, <aside> or <ul> with a layout component would remove the ARIA landmark or list semantics that assistive technology relies on. Use Box, Stack and Track where they help.

<table>, because Table is a data grid driven by columns and data props, not a substitute for hand written table markup.

Examples​

// Incorrect
<button onClick={save}>Save</button>
// ESLint: Use <Button> or <IconButton> from @adaptavant/eds-core instead of <button>

<h2>Section title</h2>
// ESLint: Use <Heading> from @adaptavant/eds-core instead of <h2>

// Correct
<Button onClick={save}>Save</Button>
<Heading as="h2">Section title</Heading>

Known false positives​

This rule encodes a recommendation rather than a contract written into the design system source, so a few legitimate patterns still trip it:

  • <a download>, where the design system link component may not support the attribute you need.
  • A <label> or <p> used for markup structure rather than as a design system surface.
  • Raw DOM in a test fixture or a story, which is often deliberate.

Suppress the rule inline in those cases, or exclude the element entirely with ignoreElements.

Options​

ignoreElements: element names this rule should skip.

{
  rules: {
    '@adaptavant/eds/prefer-eds-components': ['warn', { ignoreElements: ['input'] }],
  },
}