shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
69.84k stars 4.18k forks source link

Select Form - A form field element should have an id or name attribute #2459

Closed Jamison1 closed 7 months ago

Jamison1 commented 8 months ago

Receiving a browser issue in Chrome on the Select component

It looks like Form with Select has no id or name in SelectContent which is outside of the FormControl

                      <FormItem>
                        <div className="relative container mx-auto">
                          <FormLabel>
                            Title<sup className="text-red-600">*</sup>
                          </FormLabel>
                          <Select onValueChange={field.onChange} defaultValue={field.value} value={field.value}>
                            <FormControl enableErrorHighlight>
                              <SelectTrigger className="sm:bg-white sm:focus:bg-slate-50 focus:placeholder:text-slate-500">
                                <SelectValue placeholder="Select" />
                              </SelectTrigger>
                            </FormControl>
                            <SelectContent className="bg-white max-h-80"> ==============={Missing 'id'}
                              <SelectItem value="m@google.com">m@google.com</SelectItem>
                              <SelectItem value="m@support.com">m@support.com</SelectItem>
                            </SelectContent>
                          </Select>
                          <FormMessage />
                        </div>
                      </FormItem>

Label has correct 'for'

<label class="text-xs sm:text-sm font-normal text-slate-400 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for=":r2:-form-item">Title<sup class="text-red-600">*</sup></label>

Button has correct 'id'

<button type="button" role="combobox" aria-controls="radix-:r3:" aria-expanded="false" aria-autocomplete="none" dir="ltr" data-state="closed" data-placeholder="" class="flex h-10 w-full items-center justify-between rounded-md border border-slate-100 bg-white px-3 py-2 text-slate-600 text-base font-normal ring-offset-background placeholder:text-slate-400 placeholder:font-extralight focus:bg-white focus:outline-none focus:ring-2 focus:ring-eivalue-blue-400 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>span]:line-clamp-1 sm:bg-white sm:focus:bg-slate-50 focus:placeholder:text-slate-500" id=":r2:-form-item" aria-describedby=":r2:-form-item-description" aria-invalid="false"><span style="pointer-events: none;">Select</span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-4 w-4 opacity-50" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button>

Select is missing 'id'. It is expecting an 'id' of id=":r2:-form-item"

<select aria-hidden="true" tabindex="-1" style="position: absolute; border: 0px; width: 1px; height: 1px; padding: 0px; margin: -1px; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); white-space: nowrap; overflow-wrap: normal;"><option value=""></option><option value="m@google.com">m@google.com</option><option value="m@support.com">m@support.com</option></select>

What have I tried: Adding id=":r2:-form-item" to SelectContent or SelectItem doesnt fix the issue either.

imopbuilder commented 8 months ago

@Jamison1

You can remove the div element and shift the className attribute from div to FormItem element

<FormItem className="relative container mx-auto">
                          <FormLabel>
                            Title<sup className="text-red-600">*</sup>
                          </FormLabel>
                          <Select onValueChange={field.onChange} defaultValue={field.value} value={field.value}>
                            <FormControl enableErrorHighlight>
                              <SelectTrigger className="sm:bg-white sm:focus:bg-slate-50 focus:placeholder:text-slate-500">
                                <SelectValue placeholder="Select" />
                              </SelectTrigger>
                            </FormControl>
                            <SelectContent className="bg-white max-h-80"> ==============={Missing 'id'}
                              <SelectItem value="m@google.com">m@google.com</SelectItem>
                              <SelectItem value="m@support.com">m@support.com</SelectItem>
                            </SelectContent>
                          </Select>
                          <FormMessage />
                        </div>
                    </FormItem>
shadcn commented 7 months ago

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

WillsWebsites commented 6 months ago

I'm also running into this and not seeing a fix for it in the previous comments. It should be able to add an id to the select element so that it matches the label's htmlFor attribute and name. Been trying to figure this one out but no luck so far. @shadcn

This error shows up in the console issue warnings

imopbuilder commented 6 months ago

@WillsWebsites

Can you provide the code for this??

WillsWebsites commented 6 months ago

@imopbuilder

Sure, this is my entire item in my form:

  <FormField
    control={form.control}
    name="serviceType"
    render={({ field }) => (
      <FormItem>
        <FormLabel htmlFor="serviceType">Type of Service</FormLabel>
        <FormControl>
          <Select
            onValueChange={field.onChange}
            defaultValue={field.value}
            name={field.name}
            disabled={isSubmitting}
          >
            <SelectTrigger>
              <SelectValue placeholder="Select a service type" />
            </SelectTrigger>
            <SelectContent>
              <SelectGroup>
                <SelectLabel>Service types</SelectLabel>
                <SelectItem value={ServiceType.DIAMOND}>
                  {FormattedServiceType[ServiceType.DIAMOND]}
                </SelectItem>
                <SelectItem value={ServiceType.GOLD}>
                  {FormattedServiceType[ServiceType.GOLD]}
                </SelectItem>
                <SelectItem value={ServiceType.SILVER}>
                  {FormattedServiceType[ServiceType.SILVER]}
                </SelectItem>
                <SelectItem value={ServiceType.GENERAL}>
                  {FormattedServiceType[ServiceType.GENERAL]}
                </SelectItem>
              </SelectGroup>
            </SelectContent>
          </Select>
        </FormControl>
        <FormDescription>
          <Link href="/#packages" className="link text-gray-400">
            Learn more
          </Link>
        </FormDescription>
        <FormMessage />
      </FormItem>
    )}
  />

It should allow an id prop on the element because the native select can receive it and when it has the id it solves the issue. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select

Looking into the radix component types it doesn't look like it even allows an id for some reason unless I'm missing something. Even on the shadcn website element page it has this same console warning because they don't have an id. I mean, this isn't even a field that could be autofilled in my case but I always like to make the label htmlFor attributes match the form element's id to avoid these console warnings and for best practice.

Screenshot 2024-02-27 at 8 35 14 PM
riverkuo commented 3 months ago

@WillsWebsites

I think currently you can pass the name attribute as a workaround to solve the issue.

This is also discussed in Radix UI: https://github.com/radix-ui/primitives/discussions/2439#discussioncomment-8606491

Fingertips18 commented 2 months ago

Is there any fix for this? I hate having a form issue whenever I open the console. @shadcn

imopbuilder commented 1 month ago

@Fingertips18 and @WillsWebsites

Here is the solution

'use client';

import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select';
import { zodResolver } from '@hookform/resolvers/zod';
import Link from 'next/link';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

const ServiceType = {
    DIAMOND: 'DIAMOND',
    GOLD: 'GOLD',
    SILVER: 'SILVER',
    GENERAL: 'GENERAL',
};

const formSchema = z.object({
    serviceType: z.string().min(2, {
        message: 'Username must be at least 2 characters.',
    }),
});

export function SelectForm() {
    const form = useForm<z.infer<typeof formSchema>>({
        resolver: zodResolver(formSchema),
        defaultValues: {
            serviceType: '',
        },
    });

    return (
        <Form {...form}>
            <FormField
                control={form.control}
                name='serviceType'
                render={({ field }) => (
                    <FormItem>
                        <FormLabel htmlFor='serviceType'>Type of Service</FormLabel>
                        <FormControl>
                            <Select onValueChange={field.onChange} defaultValue={field.value} name={field.name}>
                                <SelectTrigger id='serviceType'>
                                    <SelectValue placeholder='Select a service type' />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectGroup>
                                        <SelectLabel>Service types</SelectLabel>
                                        <SelectItem value={ServiceType.DIAMOND}>{ServiceType.DIAMOND}</SelectItem>
                                        <SelectItem value={ServiceType.GOLD}>{ServiceType.GOLD}</SelectItem>
                                        <SelectItem value={ServiceType.SILVER}>{ServiceType.SILVER}</SelectItem>
                                        <SelectItem value={ServiceType.GENERAL}>{ServiceType.GENERAL}</SelectItem>
                                    </SelectGroup>
                                </SelectContent>
                            </Select>
                        </FormControl>
                        <FormDescription>
                            <Link href='/#packages' className='link text-gray-400'>
                                Learn more
                            </Link>
                        </FormDescription>
                        <FormMessage />
                    </FormItem>
                )}
            />
        </Form>
    );
}

Let me debug why there is a warning in the console. Each label element will have a htmlFor attribute which should match the id to any one of the dom-element. When there is no match then you will get this warning.

when using FormControl the FormControl component automatically passes an id to the child element, since the child element is a Select component and will not take any id attribute, there is no dom-element matching id attribute that matches the htmlFor attribute of the label.

You can fix it by passing a htmlFor value manually for both FormLabel and SelectTrigger as shown above

Notice that we are not passing the id attribute to the Select component because it will and be present in the html-dom and only SelectTrigger will be present in the dom