# Group

Connected controls with separators, text, and horizontal or vertical layouts.

- Package: `@mielui/svelte`
- Component version: `1.0.1`
- Depends on Mielui components: _internal/utils
- Shared utilities: utils.cn

## Install

~~~~sh
pnpm dlx @mielui/svelte add group
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| orientation | "horizontal" \| "vertical" \| undefined | 'horizontal' | No | No | Direction the controls are joined in. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Separator

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| orientation | "horizontal" \| "vertical" \| undefined | 'vertical' | No | No | Direction of the line. Use `vertical` between controls in a horizontal group. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Text

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| as | keyof HTMLElementTagNameMap \| undefined | 'div' | No | No | Element to render, such as `label` or `span`. Defaults to `div`. |
| for | string \| undefined | - | No | No | Id of the control a `label` element describes. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { HTMLAttributes } from 'svelte/elements';
import Root from './group.svelte';
import Separator from './group-separator.svelte';
import Text from './group-text.svelte';

export type GroupProps = Omit<HTMLAttributes<HTMLDivElement>, 'role'> & {
    /** Direction the controls are joined in. */
    orientation?: 'horizontal' | 'vertical';
};

export type GroupSeparatorProps = Omit<
    HTMLAttributes<HTMLDivElement>,
    'children' | 'role' | 'aria-hidden'
> & {
    /** Direction of the line. Use `vertical` between controls in a horizontal group. */
    orientation?: 'horizontal' | 'vertical';
};

export type GroupTextProps = HTMLAttributes<HTMLElement> & {
    /** Element to render, such as `label` or `span`. Defaults to `div`. */
    as?: keyof HTMLElementTagNameMap;
    /** Id of the control a `label` element describes. */
    for?: string;
};

export { Root, Separator, Text };
~~~~

## Examples

### Basic

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import * as Group from '@mielui/svelte/components/group';

    const revisions = ['First draft', 'Edited draft', 'Final draft'];
    let revision = $state(1);
</script>

<div class="flex flex-col items-center gap-4">
    <p class="m-0 text-sm text-foreground-muted" role="status">{revisions[revision]}</p>
    <Group.Root aria-label="Revision history">
        <Button
            variant="outline"
            disabled={revision === 0}
            onclick={() => {
                revision -= 1;
            }}
        >
            Undo
        </Button>
        <Group.Separator />
        <Button
            variant="outline"
            disabled={revision === revisions.length - 1}
            onclick={() => {
                revision += 1;
            }}
        >
            Redo
        </Button>
    </Group.Root>
</div>
~~~~

### Input

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import * as Group from '@mielui/svelte/components/group';
    import { Input } from '@mielui/svelte/components/input';

    let email = $state('');
</script>

<Group.Root aria-label="Email subscription" class="w-full max-w-sm">
    <Input type="email" aria-label="Email address" placeholder="Email address" bind:value={email} />
    <Group.Separator />
    <Button variant="outline" disabled={!email}>Subscribe</Button>
</Group.Root>
~~~~

### Menu

~~~~svelte
<script lang="ts">
    import { ArrowDown01Icon } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as DropdownMenu from '@mielui/svelte/components/dropdown-menu';
    import * as Group from '@mielui/svelte/components/group';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let action = $state('Save draft');
    let message = $state('');
</script>

<div class="flex flex-col items-center gap-3">
    <DropdownMenu.Root>
        <Group.Root aria-label="Save options">
            <Button
                variant="outline"
                onclick={() => {
                    message = action === 'Save draft' ? 'Draft saved.' : 'Changes published.';
                }}
            >
                {action}
            </Button>
            <Group.Separator />
            <DropdownMenu.Trigger variant="outline" aria-label="Choose save action" class="px-2">
                <HugeiconsIcon icon={ArrowDown01Icon} size={16} />
            </DropdownMenu.Trigger>
        </Group.Root>
        <DropdownMenu.Content>
            <DropdownMenu.Item
                callback={() => {
                    action = 'Save draft';
                }}
            >
                Save draft
            </DropdownMenu.Item>
            <DropdownMenu.Item
                callback={() => {
                    action = 'Publish';
                }}
            >
                Publish
            </DropdownMenu.Item>
        </DropdownMenu.Content>
    </DropdownMenu.Root>
    <p role="status" class="min-h-5 text-sm text-foreground-muted">{message}</p>
</div>
~~~~

### Nested

~~~~svelte
<script lang="ts">
    import { ArrowLeft01Icon, ArrowRight01Icon } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as Group from '@mielui/svelte/components/group';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let page = $state(1);
</script>

<Group.Root aria-label="Pagination">
    <Group.Root aria-label="Pages">
        {#each [1, 2, 3, 4, 5] as number (number)}
            {#if number > 1}
                <Group.Separator />
            {/if}
            <Button
                variant="outline"
                size="sm"
                class="w-8 px-0"
                aria-current={page === number ? 'page' : undefined}
                onclick={() => {
                    page = number;
                }}
            >
                {number}
            </Button>
        {/each}
    </Group.Root>
    <Group.Root aria-label="Navigation">
        <Button
            variant="outline"
            size="sm"
            class="w-8 px-0"
            aria-label="Previous page"
            disabled={page === 1}
            onclick={() => {
                page -= 1;
            }}
        >
            <HugeiconsIcon icon={ArrowLeft01Icon} size={16} />
        </Button>
        <Group.Separator />
        <Button
            variant="outline"
            size="sm"
            class="w-8 px-0"
            aria-label="Next page"
            disabled={page === 5}
            onclick={() => {
                page += 1;
            }}
        >
            <HugeiconsIcon icon={ArrowRight01Icon} size={16} />
        </Button>
    </Group.Root>
</Group.Root>
~~~~

### Popup

~~~~svelte
<script lang="ts">
    import { Settings01Icon } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as Group from '@mielui/svelte/components/group';
    import { Input } from '@mielui/svelte/components/input';
    import * as Popover from '@mielui/svelte/components/popover';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let width = $state<string | number | undefined>('1200');
    let open = $state(false);
    let message = $state('');
</script>

<div class="flex flex-col items-center gap-3">
    <Popover.Root bind:open placement="bottom-end">
        <Group.Root aria-label="Export image">
            <Button
                variant="outline"
                onclick={() => {
                    message = `Export ready at ${width}px.`;
                }}
            >
                Export
            </Button>
            <Group.Separator />
            <Popover.Trigger variant="outline" aria-label="Export settings" class="px-2">
                <HugeiconsIcon icon={Settings01Icon} size={16} />
            </Popover.Trigger>
        </Group.Root>
        <Popover.Content class="w-64" aria-label="Export settings">
            <div class="flex flex-col gap-3">
                <Input label="Width in pixels" type="number" min={1} bind:value={width} />
                <Button
                    variant="outline"
                    onclick={() => {
                        open = false;
                    }}
                >
                    Done
                </Button>
            </div>
        </Popover.Content>
    </Popover.Root>
    <p role="status" class="min-h-5 text-sm text-foreground-muted">{message}</p>
</div>
~~~~

### Text

~~~~svelte
<script lang="ts">
    import * as Group from '@mielui/svelte/components/group';
    import { Input } from '@mielui/svelte/components/input';

    const id = $props.id();
</script>

<Group.Root aria-label="Website" class="w-full max-w-xs">
    <Group.Text as="label" for={id} aria-label="Domain">https://</Group.Text>
    <Group.Separator />
    <Input {id} placeholder="example.com" />
</Group.Root>
~~~~

### Vertical

~~~~svelte
<script lang="ts">
    import { numberShuffle } from '@mielui/svelte/actions/number-shuffle';
    import { Button } from '@mielui/svelte/components/button';
    import * as Group from '@mielui/svelte/components/group';

    let zoom = $state(100);
</script>

<div class="flex items-center gap-6">
    <Group.Root orientation="vertical" aria-label="Zoom">
        <Button
            variant="outline"
            disabled={zoom === 200}
            onclick={() => {
                zoom += 25;
            }}
        >
            Zoom in
        </Button>
        <Group.Separator orientation="horizontal" />
        <Button
            variant="outline"
            disabled={zoom === 50}
            onclick={() => {
                zoom -= 25;
            }}
        >
            Zoom out
        </Button>
    </Group.Root>
    <output class="min-w-14 text-sm text-foreground-muted tabular-nums" aria-label="Zoom level">
        <span use:numberShuffle={{ value: zoom }}>{zoom}</span>%
    </output>
</div>
~~~~

For the rendered reference, visit [/docs/components/group](/docs/components/group).
