Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new

File Tree

A nested file browser with expandable folders and selectable files.

Interactive preview

The shared preview uses React Base. The source below uses Reka UI. Each example runs with local data. The preview uses React filenames. The Vue source uses app.vue and index.vue.

Installation

Complete project setup with vue/reka, then install these components:

pnpm exec frontfriend download collapsible button icon -f vue --primitive reka

Folders use Collapsible and files use Button. Tab moves between the controls. Enter or Space opens a folder or marks a file row as selected. This is a nested disclosure list, so it does not claim the arrow-key behavior of an ARIA tree widget.

Usage

Complete example

Save as FileTreeExample.vue.

<script setup lang="ts">
import { computed, ref } from "vue";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";

type FileNode = { name: string; path: string; children?: FileNode[] };
const props = withDefaults(
  defineProps<{
    label?: string;
    nodes?: FileNode[];
    nested?: boolean;
    selected?: string;
  }>(),
  { label: "Changed files", nested: false, selected: "" },
);
const emit = defineEmits<{ select: [path: string] }>();
const selection = ref("");
const current = computed(() =>
  props.nested ? props.selected : selection.value,
);
const files: FileNode[] = [
  {
    name: "app",
    path: "app",
    children: [
      { name: "app.vue", path: "app/app.vue" },
      {
        name: "settings",
        path: "app/settings",
        children: [{ name: "index.vue", path: "app/settings/index.vue" }],
      },
    ],
  },
  { name: "package.json", path: "package.json" },
];
function select(path: string) {
  selection.value = path;
  emit("select", path);
}
</script>

<template>
  <div
    :style="
      nested
        ? { display: 'grid', justifyItems: 'start', paddingInlineStart: '20px' }
        : {
            width: '100%',
            maxWidth: '560px',
            minWidth: '0',
            marginInline: 'auto',
            display: 'grid',
            gap: '16px',
            paddingBlock: '16px',
            fontSize: 'var(--text-sm)',
            lineHeight: '1.5',
          }
    "
  >
    <strong v-if="!nested">{{ label }}</strong>
    <div
      :role="nested ? undefined : 'group'"
      :aria-label="nested ? undefined : label"
      style="display: grid; justify-items: start"
    >
      <template v-for="node in nodes ?? files" :key="node.path">
        <Collapsible v-if="node.children" v-slot="{ open }" default-open>
          <CollapsibleTrigger as-child>
            <Button variant="ghost" size="sm"
              ><Icon :name="open ? 'ChevronDown' : 'ChevronRight'" /><Icon
                name="Folder"
              />{{ node.name }}</Button
            >
          </CollapsibleTrigger>
          <CollapsibleContent>
            <FileTreeExample
              :nodes="node.children"
              nested
              :selected="current"
              @select="select"
            />
          </CollapsibleContent>
        </Collapsible>
        <Button
          v-else
          :variant="current === node.path ? 'secondary' : 'ghost'"
          size="sm"
          :aria-current="current === node.path ? 'true' : undefined"
          @click="select(node.path)"
          ><Icon name="File" />{{ node.name }}</Button
        >
      </template>
    </div>
    <p
      v-if="!nested"
      role="status"
      style="
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
      "
    >
      {{ selection ? "Selected: " + selection : "" }}
    </p>
  </div>
</template>

Components and API

This composition has no separate component API. Use the APIs for its installed components.

ComponentTarget reference
collapsible Reka UI API
button Reka UI API
icon Reka UI API