# Folder Card

Folder-shaped card with a tone wash or image cover, a raised tab for the title and description, and a footer for an index and an animated count. Renders a link with href, or a whole-card button with onclick.

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

## Install

~~~~sh
pnpm dlx @mielui/svelte add folder-card
~~~~

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


### Count

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| value | number | - | Yes | No | Number to format and show. |
| class | string \| undefined | - | No | No | Classes added to the element. |

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

### Cover

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| src | string \| undefined | - | No | No | Image rendered in place of the tone wash. |
| alt | string \| undefined | '' | No | No | Image description. Defaults to empty because the title already names the folder. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Custom cover content, layered above the image or tone wash. |

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

### Description

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> | - | Yes | No | Content rendered inside. |

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

### Footer

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> | - | Yes | No | Content rendered inside. |

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

### Index

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> | - | Yes | No | Content rendered inside. |

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

### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | FolderCardLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| tone | FolderCardTone \| undefined | 1 | No | No | Chart palette color, `--chart-1` to `--chart-5`, used by the default cover wash. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> | - | Yes | No | Content rendered inside. |
| href | string \| undefined | - | No | No | Renders the card as a link. |
| disabled | boolean \| undefined | false | No | No | Blocks the click action and dims the card. Only the button form takes it. |
| onclick | MouseEventHandler<HTMLAnchorElement> \| null \| (event: MouseEvent) => void \| undefined | - | No | No | Called when it is clicked or activated. |

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

### Tab

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> | - | Yes | No | Content rendered inside. |

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

### Title

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| level | 2 \| 3 \| 4 \| undefined | 3 | No | No | Heading level in the surrounding document. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> | - | Yes | No | Content rendered inside. |

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

### Public types

~~~~ts
import type { Snippet } from 'svelte';
import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
import Root from './folder-card.svelte';
import Count from './folder-card-count.svelte';
import Cover from './folder-card-cover.svelte';
import Description from './folder-card-description.svelte';
import Footer from './folder-card-footer.svelte';
import Index from './folder-card-index.svelte';
import Tab from './folder-card-tab.svelte';
import Title from './folder-card-title.svelte';

type FolderCardTone = 1 | 2 | 3 | 4 | 5;

export type FolderCardLabels = {
    /** Text of Count, given the number and its locale-formatted form. */
    count?: (count: number, formatted: string) => string;
};

type FolderCardSharedProps = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: FolderCardLabels;
    /** Chart palette color, `--chart-1` to `--chart-5`, used by the default cover wash. */
    tone?: FolderCardTone;
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children: Snippet;
};

type FolderCardLinkProps = FolderCardSharedProps & {
    /** Renders the card as a link. */
    href: string;
    /** Blocks the click action and dims the card. Only the button form takes it. */
    disabled?: undefined;
} & Omit<HTMLAnchorAttributes, keyof FolderCardSharedProps | 'href'>;

type FolderCardStaticAttributes = Omit<
    HTMLAttributes<HTMLElement>,
    keyof FolderCardSharedProps | 'onclick'
> & {
    [Attribute in Exclude<keyof HTMLAnchorAttributes, keyof HTMLAttributes<HTMLElement>>]?: never;
};

type FolderCardButtonProps = FolderCardSharedProps & {
    href?: undefined;
    /**
     * Makes the whole card a button and runs when it is clicked or activated from the keyboard.
     * The title names the button.
     */
    onclick: (event: MouseEvent) => void;
    /** Blocks the click action and dims the card. */
    disabled?: boolean;
} & FolderCardStaticAttributes;

type FolderCardStaticProps = FolderCardSharedProps & {
    href?: undefined;
    onclick?: undefined;
    disabled?: undefined;
} & Omit<HTMLAttributes<HTMLElement>, keyof FolderCardSharedProps | 'onclick'> & {
        [Attribute in Exclude<
            keyof HTMLAnchorAttributes,
            keyof HTMLAttributes<HTMLElement>
        >]?: never;
    };

export type FolderCardProps = FolderCardLinkProps | FolderCardButtonProps | FolderCardStaticProps;

type FolderCardCoverSource =
    | {
          /** Image rendered in place of the tone wash. */
          src: string;
          /** Image description. Defaults to empty because the title already names the folder. */
          alt?: string;
      }
    | {
          src?: undefined;
          alt?: never;
      };

export type FolderCardCoverProps = FolderCardCoverSource & {
    /** Classes added to the element. */
    class?: string;
    /** Custom cover content, layered above the image or tone wash. */
    children?: Snippet;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'class'>;

export type FolderCardTabProps = {
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children: Snippet;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'class'>;

export type FolderCardTitleProps = {
    /** Heading level in the surrounding document. */
    level?: 2 | 3 | 4;
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children: Snippet;
} & Omit<HTMLAttributes<HTMLHeadingElement>, 'children' | 'class'>;

export type FolderCardDescriptionProps = {
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children: Snippet;
} & Omit<HTMLAttributes<HTMLParagraphElement>, 'children' | 'class'>;

export type FolderCardFooterProps = {
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children: Snippet;
} & Omit<HTMLAttributes<HTMLElement>, 'children' | 'class'>;

export type FolderCardIndexProps = {
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children: Snippet;
} & Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class'>;

export type FolderCardCountProps = {
    /** Number to format and show. */
    value: number;
    /** Classes added to the element. */
    class?: string;
} & Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class'>;

export { Count, Cover, Description, Footer, Index, Root, Tab, Title };
~~~~

## Examples

### Click Action

~~~~svelte
<script lang="ts">
    import * as FolderCard from '@mielui/svelte/components/folder-card';

    const folders = [
        {
            id: 'invoices',
            index: '001',
            name: 'Invoices',
            detail: 'Sent and paid',
            count: 128,
            tone: 1
        },
        {
            id: 'contracts',
            index: '002',
            name: 'Contracts',
            detail: 'Signed copies',
            count: 42,
            tone: 3
        },
        {
            id: 'archive',
            index: '003',
            name: 'Archive',
            detail: 'Read only',
            count: 906,
            tone: 5
        }
    ] as const;

    let selected = $state('Nothing opened yet');
</script>

<div class="flex w-full max-w-2xl flex-col gap-4">
    <div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
        {#each folders as folder (folder.id)}
            <FolderCard.Root
                tone={folder.tone}
                disabled={folder.id === 'archive'}
                onclick={() => {
                    selected = `Opened ${folder.name}`;
                }}
            >
                <FolderCard.Cover />
                <FolderCard.Tab>
                    <FolderCard.Title>{folder.name}</FolderCard.Title>
                    <FolderCard.Description>{folder.detail}</FolderCard.Description>
                </FolderCard.Tab>
                <FolderCard.Footer>
                    <FolderCard.Index>{folder.index}</FolderCard.Index>
                    <FolderCard.Count value={folder.count} />
                </FolderCard.Footer>
            </FolderCard.Root>
        {/each}
    </div>
    <p role="status" class="text-sm text-foreground-muted">{selected}</p>
</div>
~~~~

### Grid

~~~~svelte
<script lang="ts">
    import * as FolderCard from '@mielui/svelte/components/folder-card';

    const folders = [
        {
            index: '001',
            title: 'Client projects',
            description: 'Brand, web, and product',
            href: '/docs/components',
            tone: 1,
            files: 3957
        },
        {
            index: '002',
            title: 'Internal work',
            description: 'Processes and workflows',
            href: '/docs/installation',
            tone: 2,
            files: 465
        },
        {
            index: '003',
            title: 'Archive',
            description: 'Retired and unused files',
            href: '/studio',
            tone: 3,
            files: 164
        }
    ] as const;

    const sheets = [
        {
            lines: ['w-3/5', 'w-4/5', 'w-2/5'],
            class: 'left-[41%] translate-y-[36%] -rotate-6'
        },
        {
            lines: ['w-2/5', 'w-4/5', 'w-3/5'],
            class: 'left-[53%] translate-y-[24%] rotate-2'
        },
        {
            lines: ['w-4/5', 'w-3/5', 'w-4/5'],
            class: 'left-[66%] translate-y-[40%] rotate-8'
        }
    ];
</script>

<div class="@container w-full">
    <ul class="grid grid-cols-1 gap-4 @xl:grid-cols-2 @4xl:grid-cols-3">
        {#each folders as folder (folder.index)}
            <li class="flex">
                <FolderCard.Root href={folder.href} tone={folder.tone} class="w-full">
                    <FolderCard.Cover>
                        {#each sheets as sheet, index (index)}
                            <span
                                aria-hidden="true"
                                class={`absolute bottom-0 flex aspect-3/4 w-[30%] flex-col gap-1.5 rounded-t-[var(--radius-sm)] bg-[color-mix(in_oklab,var(--folder-card-tone)_8%,white)] px-2.5 pt-3 shadow-[var(--elevation-1)] ring-1 ring-border ${sheet.class}`}
                            >
                                {#each sheet.lines as line, row (row)}
                                    <span class={`h-1 rounded-full bg-border ${line}`}></span>
                                {/each}
                            </span>
                        {/each}
                    </FolderCard.Cover>
                    <FolderCard.Tab>
                        <FolderCard.Title>{folder.title}</FolderCard.Title>
                        <FolderCard.Description>{folder.description}</FolderCard.Description>
                    </FolderCard.Tab>
                    <FolderCard.Footer>
                        <FolderCard.Index>{folder.index}</FolderCard.Index>
                        <FolderCard.Count value={folder.files} />
                    </FolderCard.Footer>
                </FolderCard.Root>
            </li>
        {/each}
    </ul>
</div>
~~~~

### Hero

~~~~svelte
<script lang="ts">
    import * as FolderCard from '@mielui/svelte/components/folder-card';

    const sheets = [
        {
            lines: ['w-3/5', 'w-4/5', 'w-2/5'],
            class: 'left-[41%] translate-y-[36%] -rotate-6'
        },
        {
            lines: ['w-2/5', 'w-4/5', 'w-3/5'],
            class: 'left-[53%] translate-y-[24%] rotate-2'
        },
        {
            lines: ['w-4/5', 'w-3/5', 'w-4/5'],
            class: 'left-[66%] translate-y-[40%] rotate-8'
        }
    ];
</script>

<div class="w-full max-w-xs">
    <FolderCard.Root href="/docs/components" tone={1}>
        <FolderCard.Cover>
            {#each sheets as sheet, index (index)}
                <span
                    aria-hidden="true"
                    class={`absolute bottom-0 flex aspect-3/4 w-[30%] flex-col gap-1.5 rounded-t-[var(--radius-sm)] bg-[color-mix(in_oklab,var(--folder-card-tone)_8%,white)] px-2.5 pt-3 shadow-[var(--elevation-1)] ring-1 ring-border ${sheet.class}`}
                >
                    {#each sheet.lines as line, row (row)}
                        <span class={`h-1 rounded-full bg-border ${line}`}></span>
                    {/each}
                </span>
            {/each}
        </FolderCard.Cover>
        <FolderCard.Tab>
            <FolderCard.Title>Client projects</FolderCard.Title>
            <FolderCard.Description>Brand, web, and product</FolderCard.Description>
        </FolderCard.Tab>
        <FolderCard.Footer>
            <FolderCard.Index>001</FolderCard.Index>
            <FolderCard.Count value={3957} />
        </FolderCard.Footer>
    </FolderCard.Root>
</div>
~~~~

### Image Cover

~~~~svelte
<script lang="ts">
    import type { FolderCardLabels } from '@mielui/svelte/components/folder-card';
    import * as FolderCard from '@mielui/svelte/components/folder-card';

    const labels: FolderCardLabels = {
        count: (count, formatted) => {
            return `${formatted} ${count === 1 ? 'image' : 'images'}`;
        }
    };
</script>

<div class="w-full max-w-xs">
    <FolderCard.Root href="/docs/installation" {labels}>
        <FolderCard.Cover src="/og-default.png" />
        <FolderCard.Tab>
            <FolderCard.Title>Launch assets</FolderCard.Title>
            <FolderCard.Description>Social cards and screenshots</FolderCard.Description>
        </FolderCard.Tab>
        <FolderCard.Footer>
            <FolderCard.Count value={48} />
        </FolderCard.Footer>
    </FolderCard.Root>
</div>
~~~~

### Live Count

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import * as FolderCard from '@mielui/svelte/components/folder-card';

    let files = $state(465);

    function addFiles() {
        files += 24;
    }

    function reset() {
        files = 465;
    }
</script>

<div class="flex w-full max-w-xs flex-col gap-3">
    <FolderCard.Root tone={4}>
        <FolderCard.Cover />
        <FolderCard.Tab>
            <FolderCard.Title>Uploads</FolderCard.Title>
            <FolderCard.Description>Shared with the design team</FolderCard.Description>
        </FolderCard.Tab>
        <FolderCard.Footer>
            <FolderCard.Index>004</FolderCard.Index>
            <FolderCard.Count value={files} />
        </FolderCard.Footer>
    </FolderCard.Root>
    <div class="flex gap-2">
        <Button variant="outline" size="sm" onclick={addFiles}>Add 24 files</Button>
        <Button variant="ghost" size="sm" onclick={reset}>Reset</Button>
    </div>
</div>
~~~~

For the rendered reference, visit [/docs/components/folder-card](/docs/components/folder-card).
