Feature Row
Numbered feature row on a card with a trailing icon; expands when given content.
import { FeatureRow } from "@/components/feature-row/feature-row";
export default function Demo() { return ( <div className="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow index="1.2" title="Index identity" /> <FeatureRow index="1.3" title="Vault deployment" /> </div> );}<script setup lang="ts">import { FeatureRow } from "@/components/ui/feature-row";</script>
<template> <div class="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow index="1.2" title="Index identity" /> <FeatureRow index="1.3" title="Vault deployment" /> </div></template><script lang="ts"> import { FeatureRow } from "$lib/components/ui/feature-row";</script>
<div class="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow index="1.2" title="Index identity" /> <FeatureRow index="1.3" title="Vault deployment" /></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/feature-rowpnpm dlx shadcn@latest add @edmi-ui/feature-rowyarn dlx shadcn@latest add @edmi-ui/feature-rowbunx shadcn@latest add @edmi-ui/feature-rowPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,collapsible,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/feature-rowpnpm dlx shadcn-vue@latest add @edmi-ui/feature-rowyarn dlx shadcn-vue@latest add @edmi-ui/feature-rowbunx shadcn-vue@latest add @edmi-ui/feature-rowPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @lucide/vueTerminal window pnpm add @lucide/vueTerminal window yarn add @lucide/vueTerminal window bun add @lucide/vueAdd the Edmi components this one builds on:
card,collapsible,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feature-row.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feature-row.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feature-row.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feature-row.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,collapsible,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { FeatureRow } from "@/components/feature-row/feature-row";
<div className="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow index="1.2" title="Index identity" /> <FeatureRow index="1.3" title="Vault deployment" /></div><script setup lang="ts">import { FeatureRow } from "@/components/ui/feature-row";</script>
<template> <div class="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow index="1.2" title="Index identity" /> <FeatureRow index="1.3" title="Vault deployment" /> </div></template><script lang="ts"> import { FeatureRow } from "$lib/components/ui/feature-row";</script>
<div class="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow index="1.2" title="Index identity" /> <FeatureRow index="1.3" title="Vault deployment" /></div>Component source
Section titled “Component source”"use client";
import { cn } from "cn";import type * as React from "react";import { PlusIcon } from "@phosphor-icons/react";import { Card } from "@/registry/edmi/ui/card";import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/registry/edmi/ui/collapsible";import type { Elevation } from "@/registry/edmi/ui/elevation";
type FeatureRowProps = Omit<React.ComponentProps<typeof Card>, "title"> & { /** Mono number, e.g. "1.1". */ index: React.ReactNode; title: React.ReactNode; /** Trailing icon element; defaults to a plus. */ icon?: React.ReactNode; /** When set the row expands to show this content. */ children?: React.ReactNode; /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation;};
const plus = ( <PlusIcon className="size-4" />);
function FeatureRow({ className, index, title, icon = plus, children, elevation, ...props}: FeatureRowProps) { const head = ( <> <span className="font-mono text-xs text-muted-foreground">{index}</span> <span className="flex-1 text-left text-base font-medium">{title}</span> <span className="text-muted-foreground transition-transform group-data-[panel-open]/feature-row:rotate-45"> {icon} </span> </> ); if (!children) { return ( <Card data-slot="feature-row" elevation={elevation} className={cn("h-14 flex-row items-center gap-4 px-5 py-0", className)} {...props} > {head} </Card> ); } return ( <Card data-slot="feature-row" elevation={elevation} className={cn("gap-0 py-0", className)} {...props} > <Collapsible> <CollapsibleTrigger className="group/feature-row flex h-14 w-full cursor-pointer items-center gap-4 px-5 outline-none focus-visible:bg-accent"> {head} </CollapsibleTrigger> <CollapsibleContent className="px-5 pb-4 pl-[52px] text-sm text-muted-foreground"> {children} </CollapsibleContent> </Collapsible> </Card> );}
export { FeatureRow };<script setup lang="ts">import type { HTMLAttributes } from 'vue'import { Plus } from '@lucide/vue'import { Card } from '@/registry/edmi/ui/card'import type { Elevation } from '@/registry/edmi/ui/elevation'import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/registry/edmi/ui/collapsible'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2 */ elevation?: Elevation class?: HTMLAttributes['class'] /** Mono number, e.g. "1.1". */ index?: string | number title?: string}>()// Slots `index` and `title` (string props are the fallback). Default slot = expandable content; slot `icon` = trailing icon (defaults to a plus).</script>
<template> <Card :elevation="elevation" v-if="!$slots.default" data-slot="feature-row" :class="cn('h-14 flex-row items-center gap-4 px-5 py-0', props.class)" > <span class="font-mono text-xs text-muted-foreground"><slot name="index">{{ index }}</slot></span> <span class="flex-1 text-left text-base font-medium"><slot name="title">{{ title }}</slot></span> <span class="text-muted-foreground"> <slot name="icon"><Plus class="size-4" /></slot> </span> </Card> <Card :elevation="elevation" v-else data-slot="feature-row" :class="cn('gap-0 py-0', props.class)"> <Collapsible class="group/feature-row"> <CollapsibleTrigger class="flex h-14 w-full cursor-pointer items-center gap-4 px-5 outline-none focus-visible:bg-accent" > <span class="font-mono text-xs text-muted-foreground"><slot name="index">{{ index }}</slot></span> <span class="flex-1 text-left text-base font-medium"><slot name="title">{{ title }}</slot></span> <span class="text-muted-foreground transition-transform group-data-[state=open]/feature-row:rotate-45"> <slot name="icon"><Plus class="size-4" /></slot> </span> </CollapsibleTrigger> <CollapsibleContent class="px-5 pb-4 pl-[52px] text-sm text-muted-foreground"> <slot /> </CollapsibleContent> </Collapsible> </Card></template>export { default as FeatureRow } from "./FeatureRow.vue";<script lang="ts"> import { cn } from "$lib/utils.js"; import { Card } from "$lib/registry/ui/card/index.js"; import type { Elevation } from "$lib/registry/ui/elevation/index.js"; import * as Collapsible from "$lib/registry/ui/collapsible/index.js"; import PlusIcon from "phosphor-svelte/lib/Plus"; import type { ComponentProps, Snippet } from "svelte";
let { ref = $bindable(null), class: className, index, title, icon, children, elevation = "auto", ...restProps }: Omit<ComponentProps<typeof Card>, "title" | "children"> & { /** Mono number, e.g. "1.1". */ index: string | number | Snippet; title: string | Snippet; /** Trailing icon; defaults to a plus. */ icon?: Snippet; /** When set the row expands to show this content. */ children?: Snippet; /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props();</script>
{#snippet plus()} <PlusIcon class="size-4" />{/snippet}
{#if !children} <Card bind:ref {elevation} data-slot="feature-row" class={cn("h-14 flex-row items-center gap-4 px-5 py-0", className)} {...restProps}> <span class="font-mono text-xs text-muted-foreground">{#if typeof index === "function"}{@render index()}{:else}{index}{/if}</span> <span class="flex-1 text-left text-base font-medium">{#if typeof title === "function"}{@render title()}{:else}{title}{/if}</span> <span class="text-muted-foreground">{@render (icon ?? plus)()}</span> </Card>{:else} <Card bind:ref {elevation} data-slot="feature-row" class={cn("gap-0 py-0", className)} {...restProps}> <Collapsible.Root class="group/feature-row"> <Collapsible.Trigger class="flex h-14 w-full cursor-pointer items-center gap-4 px-5 outline-none focus-visible:bg-accent" > <span class="font-mono text-xs text-muted-foreground">{#if typeof index === "function"}{@render index()}{:else}{index}{/if}</span> <span class="flex-1 text-left text-base font-medium">{#if typeof title === "function"}{@render title()}{:else}{title}{/if}</span> <span class="text-muted-foreground transition-transform group-data-[state=open]/feature-row:rotate-45"> {@render (icon ?? plus)()} </span> </Collapsible.Trigger> <Collapsible.Content class="px-5 pb-4 pl-[52px] text-sm text-muted-foreground"> {@render children()} </Collapsible.Content> </Collapsible.Root> </Card>{/if}import Root from "./feature-row.svelte";
export { Root, // Root as FeatureRow,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. elevation is forwarded to the card: sunken (-1) a soft inset well, raised (+1) the bevel, floating (+2) the bevel plus one soft drop. In a layered ElevationProvider the card resolves to its role default (raised).
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
import { FeatureRow } from "@/components/feature-row/feature-row";import type { Elevation } from "@/components/ui/elevation";
function Sample({ elevation }: { elevation: Elevation }) { return ( <div className="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow elevation={elevation} index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow elevation={elevation} index="1.2" title="Index identity" /> <FeatureRow elevation={elevation} index="1.3" title="Vault deployment" /> </div> );}
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-6"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <Sample elevation={value} /> </div> ))} </div> );}<script setup lang="ts">import { FeatureRow } from "@/components/ui/feature-row";
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const,];</script>
<template> <div class="flex flex-col gap-6"> <div v-for="l in levels" :key="l.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ l.label }}</p> <div class="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow :elevation="l.value" index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow :elevation="l.value" index="1.2" title="Index identity" /> <FeatureRow :elevation="l.value" index="1.3" title="Vault deployment" /> </div> </div> </div></template><script lang="ts"> import { FeatureRow } from "$lib/components/ui/feature-row";
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const, ];</script>
<div class="flex flex-col gap-6"> {#each levels as l (l.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{l.label}</p><div class="flex w-[420px] max-w-full flex-col gap-2"> <FeatureRow elevation={l.value} index="1.1" title="Thesis and weights"> Describe the thesis, pick up to 10 assets and set their weights. </FeatureRow> <FeatureRow elevation={l.value} index="1.2" title="Index identity" /> <FeatureRow elevation={l.value} index="1.3" title="Vault deployment" /></div> </div> {/each}</div>| Part | Prop | Type | Default |
|---|---|---|---|
FeatureRow |
index |
React ReactNode; Vue slot index (prop string | number as fallback); Svelte string | number | Snippet |
required |
title |
React ReactNode; Vue slot title (string prop as fallback); Svelte string | Snippet |
required | |
icon |
React ReactNode; Vue slot icon; Svelte icon snippet |
plus | |
children |
Expandable content (Vue default slot, Svelte children). With content the 56px row becomes a Collapsible and the plus rotates to a cross |
none | |
elevation ✦ |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
✦ Edmi additions
Section titled “✦ Edmi additions”- Whole block is an Edmi addition (DESIGN §5 Patterns, board 12). Composed from: card, collapsible.
- Install:
shadcn add @edmi-ui/feature-row.