# Notch

A nonmodal panel attached to a viewport edge, with spring motion and composable content.

- Package: `@mielui/svelte`
- Component version: `1.1.0`
- Depends on Mielui components: _internal/utils, button, typography
- Shared utilities: components/_internal/theme-changes, utils.cn, utils.createContext, transition, components/_internal/surface, components/_internal/button-attributes, components/_internal/overlay-close, hugeicons-icon

## Install

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

## 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.


### Accessory

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

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

### Actions

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

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

### Close

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

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

### Content

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

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

### Description

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

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

### Header

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

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

### Peek

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | NotchLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| open | boolean \| undefined | false | No | Yes | Whether it is open. Bind it to control the state from outside. |
| mode | "triggered" \| "peek" \| undefined | 'triggered' | No | No | `triggered` opens fully. `peek` rests as a small handle that expands. |
| duration | number \| undefined | 5000 | No | No | Milliseconds it stays open before closing on its own. |
| side | "top" \| "bottom" \| "left" \| "right" \| undefined | 'top' | No | No | Viewport edge it attaches to. |
| surface | "solid" \| "glass" \| undefined | - | No | No | Surface treatment. Omit it to inherit `--mielui-surface` from the theme. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### SideAction

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| disabled | boolean \| undefined | - | No | No | Prevents activation. A disabled link loses its destination and is skipped by Tab. |
| variant | ButtonVariant \| undefined | 'panel' | No | No | Visual style. Choose it for what the action means, not for decoration. |
| size | "sm" \| "md" \| "lg" \| "icon" \| undefined | 'icon' | No | No | Control height. `icon` is a square button for a single icon. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| element | HTMLButtonElement \| HTMLAnchorElement \| undefined | - | No | Yes | Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site: |
| unstyled | boolean \| undefined | - | No | No | Skip the variant/size base classes and render with `class` alone. |
| status | ButtonStatus \| undefined | - | No | No | Controlled visual state. Loading remains focusable and refuses activation. |
| loading | boolean \| undefined | - | No | No | Convenience alias for `status="loading"`. |
| labels | ButtonLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| onclick | ((event: MouseEvent) => void) \| undefined | - | No | No | Called when the button is activated. Loading and disabled buttons do not call it. |
| onkeydown | ((event: KeyboardEvent) => void) \| undefined | - | No | No | Called on key down, before the button handles the key itself. |
| href | string \| undefined | - | No | No | Renders a link to this address instead of a button. |
| side | "start" \| "end" \| undefined | 'end' | No | No | End of the panel the action sits on. |

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

### Title

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| level | HeadingLevel \| undefined | 2 | No | No | Heading level in the surrounding document. |

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

### Public types

~~~~ts
import type { Snippet } from 'svelte';
import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
import type { ButtonProps } from '../../components/button';
import type { HeadingLevel } from '../../components/typography';
import Root from './notch.svelte';
import Accessory from './notch-accessory.svelte';
import Actions from './notch-actions.svelte';
import Close from './notch-close.svelte';
import Content from './notch-content.svelte';
import Description from './notch-description.svelte';
import Header from './notch-header.svelte';
import Peek from './notch-peek.svelte';
import SideAction from './notch-side-action.svelte';
import Title from './notch-title.svelte';

export type NotchLabels = {
    /** Accessible name of the close button. An `aria-label` on Close wins. */
    close?: string;
    /** Accessible name of the handle that expands a peeking notch. */
    expand?: string;
};

export type NotchProps = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: NotchLabels;
    /** Whether it is open. Bind it to control the state from outside. */
    open?: boolean;
    /** `triggered` opens fully. `peek` rests as a small handle that expands. */
    mode?: 'triggered' | 'peek';
    /** Milliseconds it stays open before closing on its own. */
    duration?: number;
    /** Viewport edge it attaches to. */
    side?: 'top' | 'bottom' | 'left' | 'right';
    /** Surface treatment. Omit it to inherit `--mielui-surface` from the theme. */
    surface?: 'solid' | 'glass';
    /** Content rendered inside. */
    children?: Snippet;
};
export type NotchSideActionProps = ButtonProps & { side?: 'start' | 'end' };
export type NotchPeekProps = {
    /** Content rendered inside. */
    children?: Snippet;
};
export type NotchContentProps = Omit<HTMLAttributes<HTMLDivElement>, 'popover'>;
export type NotchHeaderProps = HTMLAttributes<HTMLDivElement>;
export type NotchTitleProps = HTMLAttributes<HTMLHeadingElement> & {
    /** Heading level in the surrounding document. */
    level?: HeadingLevel;
};
export type NotchDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
export type NotchAccessoryProps = HTMLAttributes<HTMLDivElement>;
export type NotchActionsProps = HTMLAttributes<HTMLDivElement>;
export type NotchCloseProps = HTMLButtonAttributes;
export { Accessory, Actions, Close, Content, Description, Header, Peek, Root, SideAction, Title };
~~~~

## Examples

### Activity

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Gauge } from '@mielui/svelte/components/gauge';
    import * as Notch from '@mielui/svelte/components/notch';

    let open = $state(false);
    let expanded = $state(false);

    const files = [
        { name: 'Revenue.csv', size: '42 KB' },
        { name: 'Subscribers.csv', size: '18 KB' },
        { name: 'Summary.pdf', size: '96 KB' }
    ];

    function openActivity() {
        open = true;
    }

    function toggleExpanded() {
        expanded = !expanded;
    }
</script>

<Button class="mt-24" onclick={openActivity}>Show export activity</Button>
<Notch.Root bind:open side="top" duration={0}>
    <Notch.SideAction
        side="end"
        aria-label={expanded ? 'Hide export files' : 'Show export files'}
        aria-expanded={expanded}
        onclick={toggleExpanded}
    >
        <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            class="size-4"
            fill="none"
            stroke="currentColor"
            stroke-width="1.5"
            stroke-linecap="round"
        >
            <path d={expanded ? 'M5 12h14' : 'M5 12h14M12 5v14'} />
        </svg>
    </Notch.SideAction>
    <Notch.Content aria-label="Export activity" class={expanded ? 'w-112 gap-4' : 'w-84 gap-3'}>
        <div class="flex items-center gap-3">
            <Gauge value={100} size={36} tone="success" label="Export complete" />
            <Notch.Header class="min-w-0 flex-1 gap-0.5 pr-0">
                <Notch.Title class="text-sm">Export ready</Notch.Title>
                <Notch.Description class="text-xs">3 reports · 156 KB</Notch.Description>
            </Notch.Header>
        </div>
        {#if expanded}
            <ul class="flex flex-col text-sm">
                {#each files as file (file.name)}
                    <li class="flex items-center justify-between gap-4 py-1.5">
                        <span>{file.name}</span>
                        <span class="text-xs tabular-nums text-foreground-muted">{file.size}</span>
                    </li>
                {/each}
            </ul>
        {/if}
    </Notch.Content>
    <Notch.Accessory
        class="rounded-[var(--radius-control)] border border-border bg-card px-2.5 py-1 text-xs tabular-nums text-foreground-muted"
        aria-label="3 of 3 files exported"
    >
        3 of 3
    </Notch.Accessory>
</Notch.Root>
~~~~

### Glass

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Gauge } from '@mielui/svelte/components/gauge';
    import * as Notch from '@mielui/svelte/components/notch';

    let open = $state(false);

    function showGlass() {
        open = true;
    }
</script>

<Button variant="secondary" onclick={showGlass}>Show glass notch</Button>
<Notch.Root bind:open surface="glass" side="top">
    <Notch.Content
        aria-label="Workspace usage"
        class="w-80 flex-row items-center justify-between gap-4 px-8 py-5"
    >
        <Notch.Header class="gap-1 pr-0">
            <Notch.Title class="text-xs text-foreground-muted">Usage</Notch.Title>
        </Notch.Header>
        <Gauge value={41} size={40} label="Storage used" />
        <Notch.Description class="text-xs">Storage</Notch.Description>
    </Notch.Content>
</Notch.Root>
~~~~

### Hero

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Gauge } from '@mielui/svelte/components/gauge';
    import * as Notch from '@mielui/svelte/components/notch';

    const sides = ['top', 'bottom', 'left', 'right'] as const;

    let open = $state(false);
    let side = $state<(typeof sides)[number]>('top');

    const lateral = $derived(side === 'left' || side === 'right');

    function show(value: typeof side) {
        side = value;
        open = true;
    }
</script>

<div class="flex flex-wrap justify-center gap-2">
    {#each sides as position (position)}
        <Button variant="secondary" onclick={() => show(position)}>
            {position[0].toUpperCase() + position.slice(1)}
        </Button>
    {/each}
</div>
<Notch.Root bind:open {side}>
    <Notch.Content
        aria-label="Files synchronized"
        class={lateral ? 'w-28 min-h-64 items-center justify-center gap-5 px-4 py-7 text-center' : 'w-80 flex-row items-center gap-3 px-7 py-4'}
    >
        <Gauge value={100} size={36} tone="success" label="Sync complete" />
        <Notch.Header class={lateral ? 'gap-0.5 pr-0' : 'min-w-0 flex-1 gap-0.5 pr-6'}>
            <Notch.Title class="text-sm">{lateral ? 'Synced' : 'All synced'}</Notch.Title>
            <Notch.Description class="text-xs">Just now</Notch.Description>
        </Notch.Header>
        {#if lateral}
            <div class="flex flex-col items-center gap-2">
                <Gauge value={41} size={36} label="Storage used" />
                <span class="text-xs text-foreground-muted">Storage</span>
            </div>
        {/if}
    </Notch.Content>
</Notch.Root>
~~~~

### Peek

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Gauge } from '@mielui/svelte/components/gauge';
    import * as Notch from '@mielui/svelte/components/notch';

    let enabled = $state(false);

    function togglePeek() {
        enabled = !enabled;
    }
</script>

<Button variant="secondary" onclick={togglePeek}>
    {enabled ? 'Remove activity rail' : 'Pin activity rail'}
</Button>
{#if enabled}
    <Notch.Root mode="peek" side="bottom">
        <Notch.Content
            aria-label="Workspace activity"
            class="w-80 flex-row items-center justify-around gap-8 px-8 py-5 text-center"
        >
            <div class="flex flex-col items-center gap-2">
                <Gauge value={72} size={40} label="Upload progress" />
                <span class="text-xs text-foreground-muted">Uploads</span>
            </div>
            <div class="flex flex-col items-center gap-2">
                <Gauge value={41} size={40} label="Storage used" tone="success" />
                <span class="text-xs text-foreground-muted">Storage</span>
            </div>
        </Notch.Content>
        <Notch.Peek>Uploads 72%</Notch.Peek>
    </Notch.Root>
{/if}
~~~~

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