ui
ui copied to clipboard
Accessiblity Problem With Scroll Area and Forms
Hi all. I have a simple form inside a dialog, to make sure the form fit the screen it should be scrollable so I decided to use the ScrollArea component and wrap around the form.
<ScrollArea type="always" className="h-[60vh]">
<Form {...form}>
<form id="new_student" onSubmit={form.handleSubmit(onSubmit)} className="grid gap-y-5 py-4 pe-5">
<FormField
control={form.control}
name="first_name"
render={({ field }) => (
<FormItem className={formItemClass}>
<FormLabel>الاسم</FormLabel>
<div className={innerDivClass}>
<FormControl>
<Input tabIndex="0" {...field} />
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="last_name"
render={({ field }) => (
<FormItem className={formItemClass}>
<FormLabel>اسم العائلة</FormLabel>
<div className={innerDivClass}>
<FormControl>
<Input tabIndex="0" {...field} />
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem className={formItemClass}>
<FormLabel>الايميل</FormLabel>
<div className={innerDivClass}>
<FormControl>
<Input tabIndex="0" {...field} />
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
{/* other form fields */}
</ScrollArea>
When I am on an input field and I try to tab to the next input, it randomly focuses on the dialog instead of the next input.
Here is a screen gif showcasing the issue: https://imgur.com/KYys2qZ
After hours of debugging, I discovered that the ScrollArea component is causing this odd behavior. I reproduced the issue using the library's own examples so you can have a look.
"use client"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { useForm } from "react-hook-form"
import { ScrollArea } from "@/src/components/ui/scroll-area"
const formSchema = z.object({
first: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
second: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
third: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
fourth: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
fifth: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
function TestComponent() {
// 1. Define your form.
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
})
function onSubmit(values) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values)
}
return (
<>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
{/* <ScrollArea className="h-[60vh]"> */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="first"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="second"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="third"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="fourth"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="fifth"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
{/* </ScrollArea> */}
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}
export default TestComponent
I have the exact same issue with a form that has a scroll area inside. The focus while tabbing is lost to the dialog content.
I've the same bug!
As a temporary solution, add tabIndex={undefined}
to the DialogContent
or SheetContent
component.
@shadcn Hello, do you have any suggestions on how to fix this without the abIndex={undefined}
?
Other then issue with tab navigation, sometimes there is a bug when you need to click twice on next input, in order for it to focus.
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.