diff --git a/.changeset/fix-button-htmltype-default.md b/.changeset/fix-button-htmltype-default.md
new file mode 100644
index 000000000..d125236ee
--- /dev/null
+++ b/.changeset/fix-button-htmltype-default.md
@@ -0,0 +1,7 @@
+---
+'@clickhouse/click-ui': major
+---
+
+Default `htmlType` to `"button"` on `Button`, `IconButton`, and `SplitButton`. All three already accepted an `htmlType` prop, but left it undefined by default, so the native `` fell back to the browser's own default of `type="submit"`. Any of these rendered inside a `
+ );
+
+ const button = getByRole('button');
fireEvent.click(button);
+
expect(handleSubmit).toHaveBeenCalled();
});
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 98858af42..9e36ecbb3 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -33,7 +33,7 @@ export const Button = forwardRef(
(
{
type = 'primary',
- htmlType,
+ htmlType = 'button',
iconLeft,
iconRight,
align = 'center',
diff --git a/src/components/IconButton/IconButton.test.tsx b/src/components/IconButton/IconButton.test.tsx
index 8486ae67d..879f56c6f 100644
--- a/src/components/IconButton/IconButton.test.tsx
+++ b/src/components/IconButton/IconButton.test.tsx
@@ -40,7 +40,7 @@ describe('Button', () => {
});
describe('Button HTML types', () => {
- it('should not default to any type when consumer does not specify it, thus behaving as type=submit', () => {
+ it('should default to type=button so it does not submit an enclosing form', () => {
const handleSubmit = vi.fn();
const { getByRole } = renderCUI(
@@ -51,8 +51,23 @@ describe('Button', () => {
const button = getByRole('button');
+ expect(button).toHaveAttribute('type', 'button');
+ fireEvent.click(button);
expect(handleSubmit).not.toHaveBeenCalled();
+ });
+
+ it('should submit an enclosing form when htmlType="submit" is set explicitly', () => {
+ const handleSubmit = vi.fn(e => e.preventDefault());
+
+ const { getByRole } = renderCUI(
+
+ );
+
+ const button = getByRole('button');
fireEvent.click(button);
+
expect(handleSubmit).toHaveBeenCalled();
});
diff --git a/src/components/IconButton/IconButton.tsx b/src/components/IconButton/IconButton.tsx
index c697422c8..d91a8b338 100644
--- a/src/components/IconButton/IconButton.tsx
+++ b/src/components/IconButton/IconButton.tsx
@@ -26,7 +26,7 @@ const iconButtonVariants = cva(styles.iconbutton, {
});
export const IconButton = forwardRef(
- ({ type = 'primary', htmlType, icon, size, disabled, className, ...props }, ref) => {
+ ({ type = 'primary', htmlType = 'button', icon, size, disabled, className, ...props }, ref) => {
const iconName = icon ? icon.toString() : 'unknown icon';
return (
diff --git a/src/components/SplitButton/SplitButton.test.tsx b/src/components/SplitButton/SplitButton.test.tsx
index 96c7f7ad6..cc312494f 100644
--- a/src/components/SplitButton/SplitButton.test.tsx
+++ b/src/components/SplitButton/SplitButton.test.tsx
@@ -201,6 +201,24 @@ describe('SplitButton', () => {
}
);
+ it('defaults to type=button so it does not submit an enclosing form', () => {
+ const handleSubmit = vi.fn(e => e.preventDefault());
+ const { getByText } = renderCUI(
+
+ );
+
+ const button = getByText('SplitButton Main Trigger').closest('button');
+ expect(button).toHaveAttribute('type', 'button');
+
+ fireEvent.click(getByText('SplitButton Main Trigger'));
+
+ expect(handleSubmit).not.toHaveBeenCalled();
+ });
+
it('does not submit an enclosing form when htmlType="button"', () => {
const handleSubmit = vi.fn(e => e.preventDefault());
const { getByText } = renderCUI(
@@ -218,5 +236,23 @@ describe('SplitButton', () => {
expect(handleSubmit).not.toHaveBeenCalled();
});
+
+ it('submits an enclosing form when htmlType="submit" is set explicitly', () => {
+ const handleSubmit = vi.fn(e => e.preventDefault());
+ const { getByText } = renderCUI(
+
+ );
+
+ fireEvent.click(getByText('SplitButton Main Trigger'));
+
+ expect(handleSubmit).toHaveBeenCalled();
+ });
});
});
diff --git a/src/components/SplitButton/SplitButton.tsx b/src/components/SplitButton/SplitButton.tsx
index ecd503ac2..a8a25075f 100644
--- a/src/components/SplitButton/SplitButton.tsx
+++ b/src/components/SplitButton/SplitButton.tsx
@@ -8,7 +8,7 @@ import { SplitButtonProps, Menu, ButtonType } from './SplitButton.types';
export const SplitButton = ({
type = 'primary',
- htmlType,
+ htmlType = 'button',
disabled,
menu,
dir,