Eslint-plugin-jsx-a11y: control-has-associated-label error with label/input?

Created on 15 Mar 2019  Ā·  39Comments  Ā·  Source: jsx-eslint/eslint-plugin-jsx-a11y

We are getting linting errors with this rule for simple JSX such as:

<label htmlFor="foo">Foo</label>
<input id="foo" type="text" />

// or

<label>
  Foo
  <input type="text" />
</label>

Both result in Error: A control must be associated with a text label jsx-a11y/control-has-associated-label

Our rule is setup as:

'jsx-a11y/control-has-associated-label': 'error',

Do we need to pass the second argument object to the rule? or should this be supported out of the box?

help wanted

All 39 comments

OK, adding the second argument seemed to fix - however we still can't seem to satisfy this rule using a htmlFor and id to create a relationship. For example, we have a Textbox component, that renders down to an input element:

<label htmlFor="foo">My label</label>
<Textbox id="foo" />

Our config looks like:

{
  labelAttributes: ['label'],
  controlComponents: [
    'Textbox',
  ],
  ignoreElements: [
    'audio',
    'canvas',
    'embed',
    'input',
    'textarea',
     'tr',
     'video',
  ],
  ignoreRoles: [
     'grid',
     'listbox',
     'menu',
     'menubar',
     'radiogroup',
     'row',
     'tablist',
     'toolbar',
     'tree',
     'treegrid',
  ],
  depth: 2,
}

Am I missing something here?

The input has to be nested inside the label and have an id/for link in order to be maximally accessible and support all assistive devices and browsers.

It has to have both? So this rule doesn't support related labels/controls via id/for only?

It does - but you have to configure whether you want both, or just nesting, or just IDs.

However, I'd encourage you to follow the spirit of a11y by using both, so you can reach all human beings.

Ideally I'd like to configure it for either scenario, nested, both or ID's... I'm struggling to see how I configure it to support ID's.

I'd love to support doing both, but it would be too much of a limitation on our visual design where labels are often removed from the container of the control - and sadly this is out of my control.

Out of interest - if an input is wrapped in a label, why does it also need the for/ID? Are there some browsers / assistive devices that don't create a relationship of controls wrapped in a label?

@leepowellcouk yes, the latest Dragon NaturallySpeaking, i believe, requires for/ID linking, whereas for most browsers/devices, nesting is sufficient. Using either one covers 95%; the only way to get 100% is to use both.

Thanks. Just looks through the source code - I can't see any configuration options for assert as per the label-has-associated-control rule. So I'm not sure it's possible to configure to nested, for/id (both or either) in the same way as that rule - I'm sure they're intended to serve the similar purposes just coming from different directions.

See assert here:

Available options: 'htmlFor', 'nesting', 'both', 'either'

Yes that’s for ā€˜label-has-associated-control’, not ā€˜control-has-associated-label’ which is what I’m talking about šŸ™‚

On 15 Mar 2019, at 21:26, Jordan Harband notifications@github.com wrote:

See assert here:

Available options: 'htmlFor', 'nesting', 'both', 'either'

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Whoops, i think i misunderstood from the start :-) your code seems correct.

Should this be considered a feature request as per ā€˜label-has-associated-control’, - or would it be to difficult to work out the reverse relationship via id/htmlFor.

Currently we're having to disable this rule based on our code usage shown above.

cc @jessebeach - this rule might need the same ā€œassertā€ options as the inverse rule.

We are having this warning thrown up as well:

  [
    <label key="label" htmlFor="element_id">Label</label>,
    <DatePicker {...defaultProps} key="picker" id="element_id" name="element_name" />
  ]

==> 37:5 error A form label must be associated with a control jsx-a11y/label-has-associated-control

For now, we're turning off the jsx-a11y/label-has-associated-control rule.

Likewise

'jsx-a11y/label-has-associated-control': [
      'error',
      { labelComponents: ['Label'], labelAttributes: ['for'], controlComponents: ['Input'], depth: 4 },
    ],

"eslint-plugin-jsx-a11y": "~6.2.1",

Offending code:

import Label from './Label';
import Input from './Input';
... 
  const requiredProps = { children: 'Yo!', for: <Input />, id: 'abc' };

  it('renders the component when passed required props', () => {
    const { container } = render(<Label {...requiredProps} />); // <-- this line has lint error
    expect(container.firstChild).toMatchSnapshot();
  });

Tried a few variations of the for property with no success.

It’s called htmlFor, not for

Our control (for some reason) was taking in for but passing that value to htmlFor. I changed that but it didn't fix the issue.

I did fix it though:

render(<Label {...requiredProps} />);

to

render(<Label htmlFor="anyValueHere" {...requiredProps} />);

I believe I'm seeing the same thing with the following snippet on eslint-plugin-jsx-a11y 6.2.3. I believe we're going to disable the rule for now and just re-enable after it gets resolved.

          <div className="field">
            <label htmlFor="nameInput" className="label">Name</label>
            <div className="control has-icons-left">
              <input
                id="nameInput"
                className="input"
                disabled={isPosting}
                type="text"
                name="name"
                placeholder="How should we address you"
                value={name}
                onChange={this.onChange}
              />
              <span className="icon is-small is-left">
                <i className="fas fa-tag" />
              </span>
            </div>
          </div>

@fritogotlayed what issue? The input also needs to be inside the label.

@ljharb My understanding from the docs here under "Case: The label is a sibling of the control" for section "How do I resolve this error?" indicates the above snippet should be fine. Am I misunderstanding the docs or could they be out of date?

ah, the default for "assert" is indeed "either" (although for complete a11y coverage, it needs to be "both"), so yes I'd expect your snippet to work.

Can you use eslint --print-config to confirm how your rule is configured? the airbnb config, for example, sets it to "both".

It's a pretty big dump so I created a Gist.
https://gist.github.com/fritogotlayed/ac7d1ab14b09c987da85f4ae0684288a

Also, huge thanks for taking some time to help though this!

EDIT:
I just realized that the output for lint is label-has-associated-control not control-has-associated-label. I must of had word dyslexia when I commented this morning. I'm still puzzled though as to why I'm getting the error when I believe it should still work from the docs.

/[path]/src/screens/RegisterScreen.jsx
  153:13  error  A form label must be associated with a control  jsx-a11y/label-has-associated-control
  172:13  error  A form label must be associated with a control  jsx-a11y/label-has-associated-control
  193:13  error  A form label must be associated with a control  jsx-a11y/label-has-associated-control
  213:13  error  A form label must be associated with a control  jsx-a11y/label-has-associated-control

@fritogotlayed in that case, can you file a new issue for your problem, since this issue is about control-has, not label-has?

@fritogotlayed however the answer is likely because the default depth is 2; try setting it to 3.

Definitely will do. Let me try manually setting the depth first so I don't create an issue that immediately closes X-D

EDIT:
It appears the default depth I'm working with is 25. I'll create a new issue.

    "jsx-a11y/label-has-associated-control": [
      "error",
      {
        "labelComponents": [],
        "labelAttributes": [],
        "controlComponents": [],
        "assert": "both",
        "depth": 25
      }
    ],

@balibebas in general, you need the input to both be inside the label and also linked to it with htmlFor. I'm not sure why that default eslint setup would have it configured that way - the airbnb config has it set to require both, though.

No, the Airbnb config (which i maintain) did that because there's some devices that only work with nesting, and others that only work with linking, so in order to reach 100% of human beings, you have to always do both.

Yes, I'm suggesting that example shouldn't warn on any configuration. Can you provide the output of eslint --print-config path/to/file for the file that has that warning?

That's very strange; maybe Gatsby has some nonstandard way of applying an eslint config (it should be in package.json, or in .eslintrc, or .eslintrc.js, usually)

Accessibility is about many things; performance is one of the least important ones - and for development, the network effect is one of the most important.

Code:

    <label className="options-toolbar__setting-btn">
      <input
        type="checkbox"
        defaultChecked={showSimpleSKUS}
        onChange={() => {
          StorageHelper.saveToStorage('showSimpleSKUS', !showSimpleSKUS);
          setShowSimpleSKUS(!showSimpleSKUS);
        }}
      />
      Show Simple SKUs
    </label>

/app/components/Toolbar/SettingsForm.jsx

33:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
44:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
55:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
66:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
77:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
89:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
101:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control
113:9  error  A form label must be associated with a control       jsx-a11y/label-has-associated-control

https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md#case-i-just-want-a-text-label-associated-with-an-input <- doesn't seem to work...

@OZZlE depending on your config, you may need to also for-ID link the two, as well as nest them.

@OZZlE depending on your config, you may need to _also_ for-ID link the two, as well as nest them.

This is my config..

{
  "parser": "babel-eslint",
  "extends": ["airbnb"], <- are they the culprit?
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  }
}

If I still have to use htmlFor and id then it totally defeats the purpose. You nest the input inside to add a connection that Screenreaders understands and all other browsers as well. You can click the label and that will focus the input.

I think you missed that, that's the whole reason of nesting the input inside the label @ljharb šŸ˜† I don't even need it to be nested inside the label except to add a connection between label and input - the same as htmlFor & id. It makes me not forced to generate dynamic unique ids which needs custom js code like huge _lodash modules.

Yes, the airbnb config requires both nesting and for-ID linking of labels and inputs. Both are required for full a11y coverage. You also want nesting because it increases the interaction area for the input, even if things are linked.

@ljharb Alright apologies that I jumped the gun here! :-) I'm building an Admin interface and I personally believe that nesting the input inside the label suffices, how would I configure my .eslintrc to be happy with just that or that they aren't nested but has htmlFor and id - I mean it should ideally accept both if the is configurable otherwise just the first case. I can't figure out how these rules work.. Sorry, I understand if you might not have time :) Maybe someone in the community could answer otherwise :)

You'd copy the rule config from the airbnb config into your project config, and then edit it so it had the settings you like.

This is the closest I got to what I wanted (either label nests form element or uses htmlFor):

      "rules": {
        // Disabled, we only require label to nest input OR use htmlFor
        // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/b800f40a2a69ad48015ae9226fbe879f946757ed/docs/rules/label-has-associated-control.md
        "jsx-a11y/label-has-associated-control": ["off", {
          "labelComponents": [],
          "labelAttributes": [],
          "controlComponents": [],
          "assert": "both",
          "depth": 25
        }],
        // Require that Labels nest their input or uses htmlFor
        // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md
        "jsx-a11y/label-has-for": ["error", {
          "components": [],
          "required": {
            "some": ["nesting", "id"]
          },
          "allowChildren": false
        }]
      }

Unfortunately it doesn't complain if you don't use any Label at all for a form element... but it seems the be the same with the default eslint...

This is what I had to add to get either htmlFor or nesting:

"rules": {
        "jsx-a11y/control-has-associated-label": ["off", {
          "labelComponents": [],
          "labelAttributes": [],
          "controlComponents": [],
          "assert": "both",
          "depth": 25
        }],
        "jsx-a11y/label-has-associated-control": ["error", {
          "components": [],
          "required": {
            "some": ["nesting", "id"]
          },
          "allowChildren": false
        }]
}

This seems answered.

How is this answered? Most of the comments are around ā€˜label-has-associated-control’ rather than finding the reverse relationship from a control to its label with ā€˜control-has-associated-label’.

@leepowelldev ah, you're right - https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/566#issuecomment-473962198 is still the needed resolution.

this rule might need the same ā€œassertā€ options as the inverse rule

Was this page helpful?
0 / 5 - 0 ratings