Closed ziplb closed 2 years ago
Hi @ziplb,
I don't think that's a bug. As far as I know, implicit submission of forms only happens with inputs and buttons. Native checkbox/radio do not submit parent forms using the enter key. I tweaked your sandbox to use native form components instead and it definitely doesn't submit.
I will close the issue as I don't believe this is a thing, but please keep discussing if I am wrong.
As far as I know, implicit submission of forms only happens with inputs and buttons. Native checkbox/radio do not submit parent forms using the enter key.
@benoitgrelard, I've seen that mentioned elsewhere about HTML forms, though it's unclear if that's intended behavior or an actual bug in a prior implementation of the HTML Form spec.
I did a little more research and came across this example from MDN, which I adapted into this CodeSandbox. Judging by that, it does look like submitting a form on Enter
from a radio button does in fact work.
I've been trying to get this working with the RadioGroup
in Radix UI, and while I've managed to work around it by triggering a form submission onKeyDown
on the RadioGroup
, I'd prefer an option where it works more similarly to the MDN example.
Hey @zgottlieb,
Thanks for doing more research into this. It definitely looks like there are some inconsistencies between browsers.
Taking your example further, I can see that indeed the form is submitted implicitly when pressing enter on radios or checkbox in Chrome, Safari, Firefox.
However, only Firefox seems to continue to do that if I remove the submit button from the form. Chrome and Safari seem to rely on the presence of a submit button to implicitly submit on those other types of inputs.
I'm not sure where we should stand on this to be honest. Additionally I also think that in user land it's pretty rare/unknown that this is even possible (as a developer I didn't know myself).
Running into this issue as well, is there a workaround available beyond catching the keydown event and submitting the form programmatically?
I'm facing the same issue. It is very useful to be able to quickly submit a form by clicking on Enter. Here is my workaround with an useEffect hook.
"use client";
import React from "react";
import { produce } from "immer";
import {
Button,
Checkbox,
Text,
Flex,
Heading,
RadioGroup,
RadioGroupRoot,
RadioGroupItem,
Grid,
} from "@radix-ui/themes";
import * as Form from "@radix-ui/react-form";
const INITIAL_STATE = {
size: "",
toppings: {},
};
const SIZES = [
{ slug: "sm", label: 'Small (10")' },
{ slug: "md", label: 'Medium (12")' },
{ slug: "lg", label: 'Large (14")' },
{ slug: "xl", label: 'Pizza For Days (16")' },
];
const TOPPINGS = [
{ slug: "anchovies", label: "Anchovies" },
{ slug: "mushrooms", label: "Mushrooms" },
{ slug: "green-pepper", label: "Green Pepper" },
{ slug: "onions", label: "Onions" },
{ slug: "pineapple", label: "Pineapple" },
{ slug: "pepperoni", label: "Pepperoni" },
{ slug: "sausage", label: "Sausage" },
{ slug: "chicken", label: "Chicken" },
{ slug: "bacon", label: "Bacon" },
{ slug: "feta", label: "Feta" },
{ slug: "provolone", label: "Provolone" },
{ slug: "gummy-bears", label: "Gummy Bears" },
{ slug: "popcorn", label: "Popcorn" },
{ slug: "lucky-charms", label: "Lucky Charms" },
{ slug: "ice-cream", label: "Vanilla Ice Cream" },
{ slug: "cotton-candy", label: "Cotton Candy" },
];
type State = {
size: string;
toppings: Record<string, boolean>;
};
type Action =
| { type: "select-size"; slug: string }
| { type: "add-topping"; slug: string }
| { type: "remove-topping"; slug: string }
| { type: "add-all-toppings"; toppingSlugs: string[] }
| { type: "remove-all-toppings" };
function reducer(state: State, action: Action): State {
return produce(state, (draftState: any) => {
switch (action.type) {
case "select-size": {
draftState.size = action.slug;
break;
}
case "add-topping": {
draftState.toppings[action.slug] = true;
break;
}
case "remove-topping": {
delete draftState.toppings[action.slug];
break;
}
case "add-all-toppings": {
action.toppingSlugs.forEach((slug: any) => {
draftState.toppings[slug] = true;
});
break;
}
case "remove-all-toppings": {
draftState.toppings = {};
break;
}
}
});
}
export default function Home() {
console.log("render");
const id = React.useId();
const [state, dispatch] = React.useReducer(reducer, INITIAL_STATE);
function handleToggleAllToppings() {
if (hasSelectedAllToppings) {
dispatch({
type: "remove-all-toppings",
});
} else {
dispatch({
type: "add-all-toppings",
toppingSlugs: TOPPINGS.map((topping) => topping.slug),
});
}
}
console.log(state);
const hasSelectedAllToppings = TOPPINGS.every(
({ slug }) => state.toppings[slug]
);
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event?.preventDefault();
console.info(event);
window.alert(JSON.stringify(state, null, 2));
}
React.useEffect(() => {
const handleKeyDown = (event: any) => {
if (event.key === "Enter") {
event.preventDefault(); // To prevent any default behavior like submitting the form
window.alert(JSON.stringify(state, null, 2));
}
};
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [state]); // Correct dependency array
return (
<main className=" border-red-900 border min-h-full p-10 flex justify-center items-center">
<form
className=" flex flex-col gap-3 min-w-[80%] max-w-[600px]"
onSubmit={handleSubmit}
>
<Heading>Your order</Heading>
<fieldset className="border border-solid border-gray-900 p-3">
<legend>
<Text size={"2"}>Choose your size</Text>
</legend>
<RadioGroupRoot
name="size"
required={true}
onValueChange={(value) => {
dispatch({ type: "select-size", slug: value });
}}
>
<Flex gap="2" direction="column">
{SIZES.map(({ slug, label }) => (
<Text key={slug} as="label" size="1">
<label>
<Flex className="border border-500-red" gap={"2"}>
<RadioGroupItem
required={true}
value={slug}
id={`size-${slug}-${id}`}
/>
{label}
</Flex>
</label>
</Text>
))}
</Flex>
</RadioGroupRoot>
</fieldset>
<fieldset className="border border-solid border-gray-900 p-3">
<legend>
<Text size={"2"}>Select your pizza toppings</Text>
</legend>
<Grid columns="" gap="3" width="auto">
{TOPPINGS.map(({ slug, label }) => {
const hasTopping = !!state.toppings[slug];
return (
<Text as="label" size={"1"} key={slug}>
<Flex className="border border-500-red" gap="2">
<Checkbox
size={"1"}
id={`topping-${slug}-${id}`}
checked={hasTopping}
onCheckedChange={() => {
dispatch({
type: hasTopping ? "remove-topping" : "add-topping",
slug,
});
}}
/>
{label}
</Flex>
</Text>
);
})}
<Flex justify={"end"}>
<Button
variant="soft"
type="button"
onClick={handleToggleAllToppings}
>
{" "}
{hasSelectedAllToppings ? "Remove all" : "Select all"}
</Button>
</Flex>
</Grid>
</fieldset>
<Button type="submit">Take your order</Button>
<input type="submit" style={{ display: "none" }} />
</form>
</main>
);
}
Hi, is there any chance to reopen this issue?
We have the same problem with radio
, checkbox
and select
. I created a sandbox where is a FormRadix
and a FormNative
. If you select inputs with keyboard and hit enter, in FormNative
even if you are focused on radio
, checkbox
or select
the form submits.
On the other hand in FormRadix
it doesn't. With radio
and checkbox
it does nothing, but when you are focused on select
it opens. Native select
element can be opened only with Spacebar
not Enter
...is there any particular reason why it should be opened with Enter
as well?
For what it's worth, I do think it's worth tackling this discrepancy between native html elements and Radix elements.
When you do include a <button type"submit">
in a form, which I'd say is by far the most common case when using <form>
s, the behavior across browsers is consistent (Enter
on checkbox
, radio
, select
does submit the form).
And the radix "equivalent" components don't match this behavior.
I think many developers expect Radix primitives to behave the same (or at least as close as possible) to native HTML elements, and this discrepancy does introduce friction and was certainly a surprise for me when I started using Radix.
Bug report
Current Behavior
If Checkbox element is in focus, then form submit eventdoes not fire on Enter.
Expected behavior
Form submit event fires on Enter when focus is on Checkbox component.
Reproducible example
You can use the code snippet from the main documentation of Checkbox component. You need to add a submit event handler to the form:
Then you should click on the Checkbox so that it gets focus. After that, pressing enter will not cause the handler to be called.
Additional context
Most likely, this is due to the fact that the input itself doesn't receive focus, but the button responsible for styling the checkbox gets it(focus). The same issue is with RadioGroup component.
Your environment