Skip to content
SLICES / TECHNICAL FOLIO Nº 001FULL-STACK ASSEMBLIES — OPEN SOURCE

Headless / Files & documents

Organize files

Compose file actions & trash with storage permissions, loading and failure states.

42 public parts · 1examples

@slices.sh/file-storage/headlessView as Markdown ↗

Your design. Our behavior.

Explore the tree to see what each part controls. Gates reveal the relevant state; roots outline their scope.

Behavior / inputGateGenericRenderer · not headlessScope
COMPONENT ANATOMYHover · focus · tap
ONE POSSIBLE DESIGNStatic preview

Files.Container · Scope

Files.ContainerThe one place a file explorer talks to the file service.

Installation

Choose what your component needs. Run these commands in an initialized Next.js app.

0

Before you start

The steps below run in a Next.js app with shadcn set up (it has a components.json). In an existing Next.js app, such as one from create-next-app, set shadcn up first:

npx shadcn@latest init -t next -b base -p nova

Or start fresh: the slices CLI creates the app and sets it up.

npx slices.sh install
1

Install the service registry

Adds services/index.ts, which import "@/services" loads. A provider setup writes the same file, so skip this if your app has one.

npx add services
2

Install the headless library

npm

Add the React parts to your app, then compose them with your own elements.

npm install @slices.sh/file-storage@^0.2.7

Import from @slices.sh/file-storage/headless.

3

Install the service

Connect the parts to a provider. Skip this step if this service is already configured in your app.

Provider
npx add file-storage.local

Adds service configuration and API routes to your app, with the provider dependencies. For a headless composition, add import "@/services" after "use client".

The slices CLI installs the service and provisions its required resources in one command.

Provider setup notes
  • Add .data/ to .gitignore: the bytes and the index live there.
  • This store lives on one disk. A serverless deployment loses the files between requests, so deploy with @slices.sh/file-storage.s3.
  • A screen works in a bucket, a private corner of the config ("files" by default). The config lists the ones its screens work in (`buckets`, which installs grow); list more, or a prefix such as "playground/", in `buckets` in services/file-storage/rules.ts. Any other is not-found.
  • Set maxUploadBytes and accept in services/file-storage/default.config.ts: nothing is capped until you do.

Start small

Parts bring the behavior. Use their default elements, or supply your own with render.

example.tsx
"use client"import "@/services"import { Files } from "@slices.sh/file-storage/headless"export function Example() {  return (    <Files.Root>      <Files.Entries>        <Files.Entry>          <Files.SelectEntry />          <Files.EntryName />          <Files.Download>Download</Files.Download>          <Files.DeleteEntry>Delete</Files.DeleteEntry>        </Files.Entry>      </Files.Entries>      <Files.Confirming>        <Files.ConfirmDelete>Confirm deletion</Files.ConfirmDelete>        <Files.CancelDelete>Cancel</Files.CancelDelete>      </Files.Confirming>      <Files.TrashNotice>        <Files.Undo>Undo</Files.Undo>      </Files.TrashNotice>    </Files.Root>  )}

Configure a file-storage provider. Mutating actions follow the storage rules and current user permissions.

API reference

Every public part used by this experience is listed below. Gates render children without adding an element. Element parts accept native element props and render; function children receive the state declared in their props. Scopes provide context rather than a visual element.

Files.KeepSelectionscope

The selection stays among the rows on screen: a filter, a search or another folder that takes a selected row away takes it out of the selection too. The rows of a tree and the columns of a Miller view count as on screen: what was read under the open folder and still passes the filters. Otherwise the file picked in the third column would be let go of the moment it was picked. A part of its own, not something every browser does, because a browser that keeps a selection across folders (to move it, to send it) wants the rows it cannot see.

Required scope

Files.KeepSelection must be inside <Files.Root>.

Props

1 documented props

Files.KeepSelection props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/keep-selection.tsx

Files.WhenSelectedgate

Something is selected. `count` is how many, for the line that says so and the actions on them.

Required scope

Files.WhenSelected must be inside <Files.Root>.

Props

1 documented props

Files.WhenSelected props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { count: number }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/when-selected.tsx

Files.Statuscontent · <div>

The line under the listing: how many there are and what is picked. Counts the entries of the folder, not the rows on screen, which a tree makes more by every branch it opens: "12 items" is what the folder holds. Not a live region, because the announcer already says what the listing is showing, and two of them said the same thing twice.

Required scope

Files.Status must be inside <Files.Root>.

Props

2 documented props · Native <div> attributes supported

Files.Status props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: StatusState) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/status.tsx

Files.SelectEntryinput · <input>

The tick beside one row. Its accessible name is the file's, because a list of them all called "Select" reads as "Select, checkbox, unchecked" twenty times over with nothing to tell one row from another. A pattern may still name it something else; nothing here is a word anybody wrote. Shift extends from the last row touched, over the rows actually on screen — which is why this reads the listing rather than asking the selection to remember it. Whether Shift was held is a fact about the pointer, and a `change` event does not carry one, so the click that precedes it leaves the answer here for it.

Required scope

File.SelectEntry must be inside <File.Entries>.

File.SelectEntry must be inside <File.Browser>.

Props

2 documented props · Native <input> attributes supported

Files.SelectEntry props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/select-entry.tsx

Files.SelectAllinput · <input>

Everything on screen, ticked or cleared at once. Over the rows that are actually shown rather than over the folder: a bucket is longer than a page, and a control that claimed to select what has not been read would be selecting things nobody could then see it had selected. → docs/rules/capability-honesty.md Half a selection is `indeterminate`, which a native checkbox has as a property and not as an attribute, so it is written to the element rather than rendered. It was an `aria-checked="mixed"` standing in for it before, which is the statement without the fact: a browser computes this control's state from the property, and half a selection reading as "unchecked" is the half that gets cleared by accident. Nothing at all where only one row may be selected: there is no "all" to offer.

Required scope

Files.SelectAll must be inside <Files.Root>.

Props

2 documented props · Native <input> attributes supported

Files.SelectAll props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/select-all.tsx

Files.DeleteSelectedaction · <button>

Takes everything that is selected out in one go, and one Undo puts all of it back. Only where the backend has a trash that can give it back: deleting one entry for good asks first, in its row, and there is no row to ask in for several. Nothing is drawn where there is none, which is honest about what a backend can do rather than a button that asks nothing. → docs/rules/capability-honesty.md

Required scope

Files.DeleteSelected must be inside <Files.Root>.

Files.DeleteSelected requires Files.Root.

Props

2 documented props · Native <button> attributes supported

Files.DeleteSelected props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { count: number }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/delete-selected.tsx

Files.ClearSelectionaction · <button>

Lets go of everything that is selected.

Required scope

Files.ClearSelection must be inside <Files.Root>.

Props

2 documented props · Native <button> attributes supported

Files.ClearSelection props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clear-selection.tsx

Files.ContextAreacontent · <li>

A right-click menu per row: the area, the popup, items and links.

Required scope

Files.ContextArea requires Files.ContextMenu.

Props

2 documented props · Native <li> attributes supported

Files.ContextArea props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/context-area.tsx

Files.ContextMenuscope

A right-click menu per row: the area, the popup, items and links.

Required scope

Files.ContextMenu requires Files.Root.

Files.ContextMenu requires Files.Entries.

Props

1 documented props

Files.ContextMenu props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/context-menu.tsx

Files.ContextPopupcontent · <div>

A right-click menu per row: the area, the popup, items and links.

Required scope

Files.ContextPopup requires Files.ContextMenu.

Props

3 documented props · Native <div> attributes supported

Files.ContextPopup props
PropTypeDefault
popup

The menu popup and its positioner, which the pattern styles.

ReactElement<ComponentProps<typeof MotionMenu>>—
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/context-popup.tsx

Files.ContextItemaction · <button>

A right-click menu per row: the area, the popup, items and links.

Props

2 documented props · Native <button> attributes supported

Files.ContextItem props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/context-item.tsx

Files.Renamescope

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.Rename requires Files.Entries.

Files.Rename requires Files.Root.

Props

1 documented props

Files.Rename props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename.tsx

Files.Renaminggate

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.Renaming requires Files.Rename.

Props

1 documented props

Files.Renaming props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/renaming.tsx

Files.RenameFormaction · <form>

Renames the row it is in, in place. Submitting the old name does nothing.

Required scope

Files.RenameForm must be inside <Files.Entries>.

Files.RenameForm must be inside <Files.Root>.

Props

2 documented props · Native <form> attributes supported

Files.RenameForm props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename-form.tsx

Files.RenameInputinput · <input>

The new name, starting as the old one. `name="rename"` is what the form reads.

Required scope

Files.RenameInput must be inside <Files.Entries>.

Props

2 documented props · Native <input> attributes supported

Files.RenameInput props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename-input.tsx

Files.RenameSubmitaction · <button>

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.RenameSubmit requires Files.Rename.

Props

2 documented props · Native <button> attributes supported

Files.RenameSubmit props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename-submit.tsx

Files.RenameCancelaction · <button>

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.RenameCancel requires Files.Rename.

Props

2 documented props · Native <button> attributes supported

Files.RenameCancel props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename-cancel.tsx

Files.NotRenaminggate

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.NotRenaming requires Files.Rename.

Props

1 documented props

Files.NotRenaming props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/not-renaming.tsx

Files.RenameTriggeraction · <button>

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.RenameTrigger requires Files.Entries.

Files.RenameTrigger requires Files.Rename.

Props

2 documented props · Native <button> attributes supported

Files.RenameTrigger props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { name: string }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename-trigger.tsx

Files.RenameFailedgate

The rename scope of a row, gates for renaming or not, the trigger, the form with its input, save, cancel and the refusal.

Required scope

Files.RenameFailed requires Files.Rename.

Props

1 documented props

Files.RenameFailed props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { error: FileErrorSlug }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/rename-failed.tsx

Files.NewFolderaction · <button>

Makes a folder in the open one, and opens its name for typing, as a file manager does. The folder is made at once under `folderName`, or the next free name beside one that is there, and its row takes the focus with the name selected: Enter keeps it, Escape keeps what it was called. Nothing is asked first, because a form for a name is a step between wanting a folder and having one, and the row is where the name was going to be typed anyway.

Required scope

Files.NewFolder must be inside <Files.Root>.

Props

3 documented props · Native <button> attributes supported

Files.NewFolder props
PropTypeDefault
folderNameRequired
string—
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/new-folder.tsx

Files.NewFolderFormaction · <form>

The form that makes a folder. A form rather than a button with a prompt, because the name needs a label, an error and a keyboard — and `window.prompt` has none of those. The name is read from the form on submit rather than tracked on every keystroke, so the input stays uncontrolled and a skin may put anything at all around it.

Required scope

Files.NewFolderForm must be inside <Files.Root>.

Props

2 documented props · Native <form> attributes supported

Files.NewFolderForm props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/new-folder-form.tsx

Files.NewFolderNameinput · <input>

The new folder's name. `name="folder"` is what the form reads.

Props

2 documented props · Native <input> attributes supported

Files.NewFolderName props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/new-folder-name.tsx

Files.NewFolderSubmitaction · <button>

Make a folder at once and type its name in its row, or make one from a name typed first.

Props

2 documented props · Native <button> attributes supported

Files.NewFolderSubmit props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/new-folder-submit.tsx

Files.Confirminggate

This row is the one that has asked to be deleted: where the answer goes. Escape calls it off, which is what everybody presses at a question they did not mean to ask. `useEscape` watches for it only while something is armed, so there is no key being watched for the rest of the time.

Required scope

Files.Confirming must be inside <Files.Entries>.

Files.Confirming must be inside <Files.Root>.

Props

1 documented props

Files.Confirming props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { name: string; kind: "file" | "directory" }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/confirming.tsx

Files.CancelDeleteaction · <button>

Calls the deletion off. The other half of the question, and the one Escape presses.

Required scope

Files.CancelDelete must be inside <Files.Root>.

Props

2 documented props · Native <button> attributes supported

Files.CancelDelete props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/cancel-delete.tsx

Files.ConfirmDeleteaction · <button>

Deletes the row that asked, and everything under it when it is a folder. It takes the focus when it appears, because the button that was pressed to get here has just stopped existing: focus would otherwise fall back to the document and a keyboard would have to walk the whole row again to answer a question it just asked. → https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/

Required scope

Files.ConfirmDelete must be inside <Files.Confirming>.

Files.ConfirmDelete must be inside <Files.Root>.

Props

2 documented props · Native <button> attributes supported

Files.ConfirmDelete props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { name: string }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/confirm-delete.tsx

Files.DeleteEntryaction · <button>

Uses reversible trash when the backend confirms it; otherwise asks before permanent deletion.

Required scope

File.DeleteEntry must be inside <File.Entries>.

File.DeleteEntry must be inside <Files.Root>.

Files.DeleteEntry requires Files.Root.

Props

2 documented props · Native <button> attributes supported

Files.DeleteEntry props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { name: string; kind: "file" | "directory" }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/delete-entry.tsx

Files.TrashEntriesgate

The notice for the file taken out most recently, for `undoSeconds` after it was. Only the latest, because a notice per removal stacks above the list until the list is pushed off the screen. It is the notice that goes, never the receipt: Ctrl/Cmd+Z and `Files.RefreshTrash` still reach every receipt the backend holds. A receipt that was already old when this screen first had it, because it was loaded and not just made, never shows. A number that is not finite keeps the notice there until it is undone, and so does a partial one, which is a warning that something is still in place rather than a courtesy.

Capability: trash

Required scope

Files.TrashEntries requires Files.Root.

Props

2 documented props

Files.TrashEntries props
PropTypeDefault
undoSeconds
number5
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/trash-entries.tsx

Files.TrashNoticecontent · <div>

The latest receipt for undoSeconds, its notice with { name, partial, count }, Undo, the refusal with its error slug, and reloading receipts after a lost response. All render nothing where the provider has no trash.

Capability: trash

Required scope

Files.TrashNotice requires Files.TrashEntries.

Props

2 documented props · Native <div> attributes supported

Files.TrashNotice props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { name: string; partial: boolean; count: number }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/trash-notice.tsx

Files.Undoaction · <button>

The latest receipt for undoSeconds, its notice with { name, partial, count }, Undo, the refusal with its error slug, and reloading receipts after a lost response. All render nothing where the provider has no trash.

Capability: trash

Required scope

Files.Undo requires Files.Root.

Files.Undo requires Files.TrashEntries.

Props

2 documented props · Native <button> attributes supported

Files.Undo props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { name: string; pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/undo.tsx

Files.TrashFailedgate

The latest receipt for undoSeconds, its notice with { name, partial, count }, Undo, the refusal with its error slug, and reloading receipts after a lost response. All render nothing where the provider has no trash.

Capability: trash

Required scope

Files.TrashFailed requires Files.Root.

Props

1 documented props

Files.TrashFailed props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { error: FileErrorSlug }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/trash-failed.tsx

Files.RefreshTrashaction · <button>

The latest receipt for undoSeconds, its notice with { name, partial, count }, Undo, the refusal with its error slug, and reloading receipts after a lost response. All render nothing where the provider has no trash.

Capability: trash

Required scope

Files.RefreshTrash requires Files.Root.

Props

2 documented props · Native <button> attributes supported

Files.RefreshTrash props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/refresh-trash.tsx

Files.Clashgate

A name is taken where something was going, and somebody has to say what happens. One gate for the two ways it happens, a file on its way in and an entry being moved, since to the person they are one question: it is there while any is waiting and says the first. The answers are `Files.ClashReplace`, `Files.ClashKeepBoth` and `Files.ClashSkip`, for this one or, with `Files.ClashAll` ticked, for every one waiting and the ones of this batch still to come. Escape is skip, as it is the answer that leaves everything as it was.

Props

1 documented props

Files.Clash props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: ClashSaid) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clash.tsx

Files.ClashReplaceaction · <button>

Writes the new one over the one that is there.

Props

2 documented props · Native <button> attributes supported

Files.ClashReplace props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: ClashSaid) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clash-replace.tsx

Files.ClashKeepBothaction · <button>

Gives the new one the next free name beside the one that is there. The answer that loses nothing.

Props

2 documented props · Native <button> attributes supported

Files.ClashKeepBoth props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: ClashSaid) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clash-keep-both.tsx

Files.ClashSkipaction · <button>

Leaves the new one out and the one that is there as it was.

Props

2 documented props · Native <button> attributes supported

Files.ClashSkip props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: ClashSaid) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clash-skip.tsx

Files.ClashMoregate

More may follow this question, so "apply to all" has something to apply to. Around the words and the box that offer it.

Required scope

Files.ClashMore must be inside <Files.Clash>.

Props

1 documented props

Files.ClashMore props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clash-more.tsx

Files.ClashAllinput · <input>

"Apply to all": the answer given next is the answer for every name that is taken in this batch. Put inside `Files.ClashMore`, which is where there is something to apply it to.

Required scope

Files.ClashAll must be inside <Files.Clash>.

Props

2 documented props · Native <input> attributes supported

Files.ClashAll props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/clash-all.tsx

Files.DropIntocontent · <div>

An entry that can be picked up, and a folder that can be dropped into. One part for both, because on screen they are one gesture and every row is capable of each: a file is carried and a folder receives, and a folder is carried too. Splitting them would leave a row choosing between two parts by its own kind, which is the conditional that gates exist to remove. → docs/rules/states-gate-themselves.md Keyboard as well as pointer, on the same reasoning as the board's `Collection.Row`: Enter or Space picks a row up and puts it down, Escape drops it where it was. A drag that is only a drag is a feature that nobody without a mouse has. Which is why focus marks a target as well as the pointer does. `dragenter` fires for a pointer alone, so a row carried by a keyboard used to travel over folder after folder with none of them lit and nothing said — the gesture worked and gave no sign that it would. `Files.CarryAnnouncer` is the other half of that. Which drops are refused is `canReceive`, beside the hook, because it is path grammar and a part reaches a service for types alone. Files from outside the page are the other drop on a folder's row: where there is an upload queue, the files and folders dropped on a folder go into it, as they do into the open folder when dropped on the listing — through the same limits, and into the same queue, so what is on its way is drawn where it is going. `move={false}` leaves the carrying out for a screen that may upload and may not change what is there. A row that is itself an upload on its way is neither carried nor dropped on. `data-drop` says a folder is about to receive something, `data-invalid` that what is over it is nothing it takes; `data-active` says this row is the one in the air.

Required scope

Files.DropInto must be inside <Files.Entries>.

Files.DropInto must be inside <Files.Root>.

Props

3 documented props · Native <div> attributes supported

Files.DropInto props
PropTypeDefault
move
booleantrue
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/drop-into.tsx

Files.CarryAnnouncercontent · <div>

What is in the air, for somebody who cannot see it. `Files.DropInto` can be worked with a keyboard — Enter picks a row up, Enter on a folder puts it down, Escape leaves it where it was — and every part of that was silent: a row lifted with no pointer said nothing, and the folder it was over was a ring nobody could see. A gesture whose whole feedback is a highlight is a gesture only a sighted mouse has. Its own region rather than a line in `Files.Announcer`, because a carry and a listing are two different things happening to the same rows and one of them runs while the other is being read out. The words are the pattern's, and so is whether to say them at all: a configuration with nothing to move never renders this. → docs/rules/words-in-copy-looks-in-skin.md

Required scope

Files.CarryAnnouncer must be inside <Files.Root>.

Props

2 documented props · Native <div> attributes supported

Files.CarryAnnouncer props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: CarrySaid) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/carry-announcer.tsx

Files.Containerscope

The one place a file explorer talks to the file service. It maps the client onto what the screen below draws: the folder that is open and its pages (`Files.Root`), the folders read besides it, from the store's listings or from `loadChildren`, and the address of every file and every picture, which the parts ask the client for and the server signs. What the screen may do is read here too, from what the client has: a part that needs `upload` asks `Files.CanUpload`, so a service with none gets a browser with no upload button and no drop, and not a button that fails when pressed. The screen never calls the service itself: pattern code calls no hook, and every part under here reaches the service through this container's contexts. → docs/rules/patterns-read-no-state.md

Props

2 documented props

Files.Container props
PropTypeDefault
loadChildren

Where a folder's children come from when it is not the store's own listing.

LoadChildren—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Also inherits Omit<FilesRootProps, "children">. See TypeScript for the complete declaration.

Source: @slices.sh/file-storage/src/headless/container.tsx

Files.Rootscope

The browser's root, and Files.Root with Files.Children in one, the seam that reads what the config's client can do.

Props

9 documented props

Files.Root props
PropTypeDefault
config

The file-storage config, a slug from the registry (services/file-storage/<config>.config.ts). The nearest root's, else "default", when left out.

string—
enabled

Left out or true: required, and a missing service throws. "optional": renders its children and every part inside renders nothing except Files.Unavailable, and a Composer inside finds its sink switched off. false: off. A capability name such as "trash" requires the service with it.

Enabled—
bucket
string—
initialPath

Where the browser opens. Give path and onPath instead to keep it in the address.

string"/"
path
string—
multiple
booleantrue
searchLeavesWithFolder

Opening another folder ends a search, as in Finder, instead of carrying it into the folder.

booleanfalse
ifExists

What moving an entry into a folder that has one of that name does. Left out, it is refused. "ask" raises a question for Files.Clash to put to the person: replace, keep both or skip.

IfExists | "ask"—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/files-root.tsx

Files.Listedgate

The folder is being listed, or has been: where its headings and its rows go. Everything a listing is made of belongs inside this — the column headings, the skeletons that wait for the rows, the rows, the empty state that stands in for them, the page after this one — so that they share one place in the layout. A folder with nothing in it then keeps its headings over a body that says so, rather than a sentence above headings with nothing under them. Nothing when the read failed, where `Files.Failed` says why and headings over no rows would say nothing, nor where no backend is installed, where `Files.Unavailable` is the answer.

Required scope

Files.Listed must be inside <Files.Root>.

Props

1 documented props

Files.Listed props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/listed.tsx

Files.Entriesscope

What the browser is showing: the directory's children, or what the search found. Under a folder that is open, in a tree, its entries follow it with how far in they are. One part for both, because a screen that drew them separately would be two lists whose rows, selection and actions drifted apart — and a search that showed a different kind of row than the folder it searched is the thing people report as a bug. Where the screen draws uploads in place, each one that is on its way is a row among these, in the order of the rest, with `Files.Pending` to say how far it has got. A row that is not there yet cannot be opened, picked, renamed or moved: its parts say so themselves.

Props

1 documented props

Files.Entries props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/entries.tsx

Files.Entrycontent · <li>

The gate around everything a listed folder draws, the repeater of rows on screen, the row, the next page, roving keys over the rows, a windowed list for long folders and the live announcement of what the folder now holds.

Required scope

Files.Entry must be inside <Files.Entries>.

Files.Entry must be inside <Files.Root>.

Props

2 documented props · Native <li> attributes supported

Files.Entry props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: EntryState) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/file-storage/src/headless/entry.tsx

When something is missing

No service configuration
Install the file-storage setup below and import "@/services". Required service roots report missing setup; gates do not replace that configuration.
Missing capability or parent scope
Follow each part’s context requirements and the example composition. Capability-aware controls hide when the provider cannot perform their action.
Read the setup guide →