final-form / react-final-form

🏁 High performance subscription-based form state management for React
https://final-form.org/react
MIT License
7.39k stars 481 forks source link

The <label> elements in examples are not connected to their fields #401

Open lydell opened 5 years ago

lydell commented 5 years ago

In the example in the readme, as well as some (all?) codesandbox examples (such as https://codesandbox.io/s/yk1zx56y5j), use markup like this:

<div>
  <label>Label</label>
  <Field />
</div>

But that doesn't connect the label to the field. Most notably, it's not possible to click the label text to focus the field. And I guess screen readers won't announce what the fields are for.

To connect a label to a field, you can either use IDs:

<div>
  <label for="myfield">Label</label>
  <Field id="myfield" />
</div>

Or nesting (might require slightly changing styles):

<label>
  <span>Label</span>
  <Field />
</label>

People often copy-paste from examples, so I think it would be nice if they don’t include this mistake.

Thanks!

(This is probably a good first issue for aspiring contributors.)

pantchox commented 5 years ago

wondering for the same exact issue, how do we use the "for" attribute with the "Field" component?

Noitidart commented 5 years ago

First issue I hit when I came to final-form. Is there a way to "auto-id" and set the "for" to be that?

Noitidart commented 5 years ago

What I did was useMemo with an empty array so it never recalculates the id - https://stackoverflow.com/q/55309140/1828637

I don't like it. I hoped that the form lib would take care of this for us.

import React, { useMemo } from 'react';
import { Form, Field } from 'react-final-form';
import { uniqueId } from 'lodash';

function TaskForm() {

    const id = useMemo(() => uniqueId('_form'), []);
    const getFor = name => name + id;

    return (
        <>
            <h3>Create a task</h3>
            <Form onSubmit={onSubmit}>
                {({ handleSubmit, pristine, invalid, ...rest }) => {(
                    <form onSubmit={handleSubmit}>
                        <div className="form-group">
                            <label htmlFor={getFor('firstName')}>First Name</label>
                            <Field name="firstName" id={getFor('firstName')} component="input" placeholder="First Name" />
                        </div>

                        <button type="submit" disabled={pristine || invalid}>Submit</button>
                    </form>
                )}}
            </Form>
        </>
    );
}
lydell commented 5 years ago

If possible in your case, I find it the easiest to nest the input inside the <label> element – no need for IDs.

Noitidart commented 5 years ago

@lydell Oh wow thank you very much. Is this a standard approach? I never knew about it.

Noitidart commented 5 years ago

Gosh, bootstrap styles are expecting my label be separate. :(

lydell commented 5 years ago

Yes, it's standard.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label

To associate the <label> with an <input> element, you need to give the <input> an id attribute. The <label> then needs a for attribute whose value is the same as the input's id.

Alternatively, you can nest the <input> directly inside the <label>, in which case the for and id attributes are not needed because the association is implicit