Button Group

A component that groups multiple buttons together with shared borders and styling.

button
  • Rust/UI Icons - CopyCopy Demo
use leptos::prelude::*;

use crate::components::ui::button::{Button, ButtonVariant};
use crate::components::ui::button_group::ButtonGroup;

#[component]
pub fn DemoButtonGroup() -> impl IntoView {
    view! {
        <ButtonGroup>
            <Button variant=ButtonVariant::Outline>"First"</Button>
            <Button variant=ButtonVariant::Outline>"Second"</Button>
            <Button variant=ButtonVariant::Outline>"Third"</Button>
        </ButtonGroup>
    }
}

Installation

You can run either of the following commands:

# cargo install ui-cli --force
ui add demo_button_group
ui add button_group

Update the imports to match your project setup.

Copy and paste the following code into your project:

components/ui/button_group.rs

use leptos::prelude::*;
use leptos_ui::clx;
use tw_merge::*;

use super::separator::{Separator, SeparatorOrientation};

mod components {
    use super::*;
    clx! {ButtonGroupText, span, "bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4"}
}

pub use components::*;

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[component]
pub fn ButtonGroup(
    #[prop(into, optional)] orientation: Signal<ButtonGroupOrientation>,
    #[prop(into, optional)] class: String,
    children: Children,
) -> impl IntoView {
    let merged_class = Memo::new(move |_| {
        let orientation = orientation.get();
        let button_group = ButtonGroupClass { orientation };
        button_group.with_class(class.clone())
    });

    view! {
        <div data-name="ButtonGroup" role="group" class=merged_class>
            {children()}
        </div>
    }
}

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[derive(TwClass, Default)]
#[tw(
    class = "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2"
)]
pub struct ButtonGroupClass {
    pub orientation: ButtonGroupOrientation,
}

#[derive(TwVariant)]
pub enum ButtonGroupOrientation {
    #[tw(
        default,
        class = "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none"
    )]
    Horizontal,
    #[tw(
        class = "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none"
    )]
    Vertical,
}

#[component]
pub fn ButtonGroupSeparator(
    #[prop(into, optional, default = SeparatorOrientation::Vertical.into())] orientation: Signal<SeparatorOrientation>,
    #[prop(into, optional)] class: String,
) -> impl IntoView {
    let merged_class = tw_merge!("relative !m-0 self-stretch data-[orientation=vertical]:h-auto", class);

    view! { <Separator attr:data-name="ButtonGroupSeparator" orientation=orientation class=merged_class /> }
}

Update the imports to match your project setup.

Usage

use crate::components::ui::button::{Button, ButtonVariant};
use crate::components::ui::button_group::ButtonGroup;
<ButtonGroup>
    <Button variant=ButtonVariant::Outline>"First"</Button>
    <Button variant=ButtonVariant::Outline>"Second"</Button>
    <Button variant=ButtonVariant::Outline>"Third"</Button>
</ButtonGroup>

Examples

With Separator

Add visual separators between buttons using ButtonGroupSeparator.

  • Rust/UI Icons - CopyCopy Demo
use leptos::prelude::*;

use crate::components::ui::button::{Button, ButtonVariant};
use crate::components::ui::button_group::{ButtonGroup, ButtonGroupSeparator};

#[component]
pub fn DemoButtonGroupSeparator() -> impl IntoView {
    view! {
        <ButtonGroup>
            <Button variant=ButtonVariant::Secondary>"Copy"</Button>
            <ButtonGroupSeparator />
            <Button variant=ButtonVariant::Secondary>"Paste"</Button>
            <ButtonGroupSeparator />
            <Button variant=ButtonVariant::Secondary>"Cut"</Button>
        </ButtonGroup>
    }
}

With Icons

Button groups work great with icon buttons, including vertical orientation.

  • Rust/UI Icons - CopyCopy Demo
use icons::{Minus, Plus};
use leptos::prelude::*;

use crate::components::ui::button::{Button, ButtonSize, ButtonVariant};
use crate::components::ui::button_group::{ButtonGroup, ButtonGroupOrientation};

#[component]
pub fn DemoButtonGroupIcon() -> impl IntoView {
    view! {
        <ButtonGroup orientation=ButtonGroupOrientation::Vertical>
            <Button variant=ButtonVariant::Outline size=ButtonSize::Icon>
                <Plus />
            </Button>
            <Button variant=ButtonVariant::Outline size=ButtonSize::Icon>
                <Minus />
            </Button>
        </ButtonGroup>
    }
}