[BUG] - ButtonGroup styles affects nested children
NextUI Version
2.2.9
Describe the bug
Buttons nested deeply in a ButtonGroup are still styled as a ButtonGroup.
e.g.
<ButtonGroup>
<Button onPress={onOpen}>Open Modal<Button>
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent>
<ModalFooter>
{/* These buttons will be styled as if they were in a ButtonGroup */}
<Button>Close</Button>
<Button>Submit</Button>
</ModalFooter>
</ModalContent>
</Modal>
</ButtonGroup>
Your Example Website or App
No response
Steps to Reproduce the Bug or Issue
- Create a button group
- Create a modal and place the trigger button and the modal component within the button group
- Create buttons in the modal
Expected behavior
I expect that the buttons are styled normally, e.g. not connected as if it were a button group.
Screenshots or Videos
Operating System Version
macOS
Browser
Chrome
I do not think this is a bug.
You can do like this:
I do not think this is a bug.
You can do like this:
![]()
I should update the example to be more comprehensive then. I have two modals, A & B. Both files look like this:
export const Trigger = () => (
<Button>Open Modal</Button>
<Modal>...</Modal>
)
And used like so:
<ButtonGroup>
<TriggerA/>
<TriggerB/>
</ButtonGroup>
I hope that clears it up.
Having the same issue. Would appreciate a workaround
Issue is still there on heroui, would like a fix 🙏 I think only direct children buttons of buttongroup should be impacted by buttongroup
@AntoineArt can you provide your code as well? Also it shouldn't Modal in ButtonGroup.
@AntoineArt can you provide your code as well? Also it shouldn't
ModalinButtonGroup.
I don't use this library anymore (moved to using headless UI libraries like ariakit & rac) but if the modal is rendered inside of portal as a child of the body then the styles shouldn't affect it. So you should check for that.
@AntoineArt can you provide your code as well? Also it shouldn't
ModalinButtonGroup.
Hi, here is a minimal working version :
'use client';
import {
Button,
ButtonGroup,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
} from '@heroui/react';
import React from 'react';
export const Content = () => {
return (
<div>
<EmployeesTableDemo />
</div>
);
};
// Simplified interfaces
interface IUserProfile {
id: string;
first_name: string;
last_name: string;
email: string;
phone_number?: string;
is_active: boolean;
}
interface EmployeeDetailsModalProps {
data?: IUserProfile;
tooltipText?: string;
button: React.ReactNode;
modalTitle: string;
}
// Sample data
const MOCK_USERS: IUserProfile[] = [
{
id: '1',
first_name: 'John',
last_name: 'Doe',
email: '[email protected]',
phone_number: '1234567890',
is_active: true,
},
{
id: '2',
first_name: 'Jane',
last_name: 'Smith',
email: '[email protected]',
is_active: true,
},
];
const COLUMNS = [
{ name: 'Name', uid: 'name' },
{ name: 'Email', uid: 'email' },
{ name: 'Phone', uid: 'phone_number' },
{ name: 'Actions', uid: 'actions' },
];
function EmployeeDetailsModal({ data, tooltipText, button, modalTitle }: EmployeeDetailsModalProps) {
const [isOpen, setIsOpen] = React.useState(false);
return (
<>
{React.cloneElement(button as React.ReactElement, {
onPress: () => setIsOpen(true),
})}
<Modal
isOpen={isOpen}
onOpenChange={setIsOpen}
size='2xl'
>
<ModalContent>
{(onClose) => (
<>
<ModalHeader>{modalTitle}</ModalHeader>
<ModalBody>
<Input
label='First Name'
value={data?.first_name}
variant='bordered'
/>
<Input
label='Last Name'
value={data?.last_name}
variant='bordered'
/>
<Input
label='Email'
value={data?.email}
variant='bordered'
/>
<Input
label='Phone'
value={data?.phone_number}
variant='bordered'
/>
</ModalBody>
<ModalFooter>
<Button
color='danger'
variant='light'
onPress={onClose}
>
Cancel
</Button>
<Button
color='primary'
onPress={onClose}
>
Save Changes
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
</>
);
}
export default function EmployeesTableDemo() {
const [users] = React.useState<IUserProfile[]>(MOCK_USERS);
const handleDelete = (id: string) => {
console.log('Delete user:', id);
};
const RenderCell = ({ user, columnKey }: { user: IUserProfile; columnKey: string }) => {
switch (columnKey) {
case 'name':
return `${user.first_name} ${user.last_name}`;
case 'actions':
return (
<ButtonGroup
variant='bordered'
size='sm'
>
<EmployeeDetailsModal
data={user}
tooltipText='Edit user details'
modalTitle={`Edit ${user.first_name} ${user.last_name}`}
button={
<Button
// size='sm'
// variant='bordered'
>
Edit
</Button>
}
/>
<Button
color='danger'
onPress={() => handleDelete(user.id)}
>
Delete
</Button>
</ButtonGroup>
);
default:
return user[columnKey as keyof IUserProfile] || <i>Not provided</i>;
}
};
return (
<div className='p-4'>
<div className='mb-4'>
<EmployeeDetailsModal
tooltipText='Add new employee'
modalTitle='Add New Employee'
button={<Button color='primary'>Add Employee</Button>}
/>
</div>
<Table aria-label='Employees table'>
<TableHeader>
{COLUMNS.map((column) => (
<TableColumn key={column.uid}>{column.name}</TableColumn>
))}
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
{COLUMNS.map((column) => (
<TableCell key={`${user.id}-${column.uid}`}>
<RenderCell
user={user}
columnKey={column.uid}
/>
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
You can see that if modal's trigger button is in a buttongroup (edit button in table), children buttons are also styled as if in a buttongroup.
It is probably due to my button being a prop, but I need it in order to reuse my modal at several places.
👀