eslint-plugin-jsx-a11y icon indicating copy to clipboard operation
eslint-plugin-jsx-a11y copied to clipboard

control-has-associated-label error with label/input?

Open leepowelldev opened this issue 6 years ago • 40 comments
trafficstars

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?

leepowelldev avatar Mar 15 '19 11:03 leepowelldev

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?

leepowelldev avatar Mar 15 '19 14:03 leepowelldev

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.

ljharb avatar Mar 15 '19 15:03 ljharb

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

leepowelldev avatar Mar 15 '19 16:03 leepowelldev

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.

ljharb avatar Mar 15 '19 17:03 ljharb

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.

leepowelldev avatar Mar 15 '19 18:03 leepowelldev

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?

leepowelldev avatar Mar 15 '19 18:03 leepowelldev

@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.

ljharb avatar Mar 15 '19 19:03 ljharb

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.

leepowelldev avatar Mar 15 '19 21:03 leepowelldev

See assert here:

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

ljharb avatar Mar 15 '19 21:03 ljharb

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 [email protected] 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.

leepowelldev avatar Mar 15 '19 21:03 leepowelldev

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

ljharb avatar Mar 15 '19 22:03 ljharb

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.

leepowelldev avatar Mar 18 '19 11:03 leepowelldev

cc @jessebeach - this rule might need the same “assert” options as the inverse rule.

ljharb avatar Mar 18 '19 15:03 ljharb

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.

soundasleep avatar May 20 '19 04:05 soundasleep

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.

jcollum-nike avatar May 20 '19 22:05 jcollum-nike

It’s called htmlFor, not for

ljharb avatar May 21 '19 01:05 ljharb

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} />);

jcollum-nike avatar May 21 '19 15:05 jcollum-nike

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 avatar Aug 15 '19 14:08 fritogotlayed

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

ljharb avatar Aug 15 '19 15:08 ljharb

@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?

fritogotlayed avatar Aug 15 '19 17:08 fritogotlayed

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".

ljharb avatar Aug 15 '19 20:08 ljharb

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 avatar Aug 15 '19 21:08 fritogotlayed

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

ljharb avatar Aug 15 '19 21:08 ljharb

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

ljharb avatar Aug 15 '19 21:08 ljharb

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
      }
    ],

fritogotlayed avatar Aug 15 '19 21:08 fritogotlayed

@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.

ljharb avatar May 30 '20 20:05 ljharb

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?

ljharb avatar May 30 '20 20:05 ljharb

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.

ljharb avatar May 30 '20 21:05 ljharb

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 avatar Oct 13 '20 13:10 OZZlE

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

ljharb avatar Oct 13 '20 18:10 ljharb