# File and Folder Access in React Web Apps

Browsers protect user files by default, so your React app can't scan a user's disk. Modern web APIs let users explicitly share files and folders with your app — safely and with granular permissions. This post shows practical patterns, code examples, and fallbacks so you can build file-enabled web apps that feel native.

## How file access works in the browser (short version)

Browsers act as a security gate between web apps and the filesystem. A web app must ask the user to pick files or folders; it cannot arbitrarily read the disk. This permission-based model keeps users safe while letting apps work with user-selected data.

Summary: The browser enforces explicit user choice and scoped access.

* * *

## Quick patterns overview

*   input\[type="file"\] — the universal fallback for file selection.
    
*   Drag-and-drop — convenient UX for selecting files.
    
*   webkitdirectory / webkitRelativePath — folder-like upload support in some browsers.
    
*   File System Access API — modern API to pick files/folders and read/write them with handles.
    

Summary: Use progressive enhancement — start with input\[type="file"\], add drag-and-drop, and enable File System Access API when available.

* * *

## 1) Basic file selection with input\[type="file"\]

This is the most compatible approach. Let the user pick files, then read them with the File API.

Example: single file read as text

```javascript
function handleFileInput(event) {
  const file = event.target.files[0];
  if (!file) return;
  file.text().then(text => {
    console.log('Contents:', text.slice(0, 200));
  });
}
```

Example: multiple files

```html
<input id="files" type="file" multiple />
```

```javascript
document.getElementById('files').addEventListener('change', (e) => {
  for (const file of e.target.files) {
    // file.name, file.size, file.type
  }
});
```

Notes:

*   file.text() returns a Promise.
    
*   For binary streams, use file.arrayBuffer() or file.stream().
    

Summary: input\[type="file"\] works everywhere and is the baseline for file input.

* * *

## 2) Folder selection: webkitdirectory vs File System Access API

Two ways to let users pick folders:

A) Non-standard but widely supported input attribute (useful fallback)

```html
<input type="file" id="folder" webkitdirectory multiple />
```

Files returned include file.webkitRelativePath which shows folder structure.

B) File System Access API (modern, more control)

```javascript
// feature-detect first
if ('showDirectoryPicker' in window) {
  const dirHandle = await window.showDirectoryPicker();
  for await (const [name, handle] of dirHandle.entries()) {
    if (handle.kind === 'file') {
      const file = await handle.getFile();
      console.log(file.name, file.size);
    }
  }
}
```

Summary: Use webkitdirectory for broad compatibility; use showDirectoryPicker for richer, native-like folder access when available.

* * *

## 3) Drag-and-drop file handling

Drag-and-drop gives fast UX and works with both files and folders (where the browser exposes folder entries).

Simple drop handler

```javascript
function onDrop(e) {
  e.preventDefault();
  const items = e.dataTransfer.items;
  for (const item of items) {
    if (item.kind === 'file') {
      const file = item.getAsFile();
      // process file
    }
  }
}
```

Tip: call e.preventDefault() on dragover to allow drop. If you need folder recursion, use DataTransferItem.webkitGetAsEntry (non-standard) or rely on the File System Access API.

Summary: Drag-and-drop improves UX; combine it with input fallbacks for accessibility.

* * *

## 4) Writing files: saving with the File System Access API

The File System Access API (an Application Programming Interface — API — that exposes file handles) lets you create and save files without an upload round trip.

Save a text file:

```javascript
async function saveText(filename, contents) {
  const handle = await window.showSaveFilePicker({ suggestedName: filename });
  const writable = await handle.createWritable();
  await writable.write(contents);
  await writable.close();
}
```

If showSaveFilePicker isn't available, fall back to creating a download link:

```javascript
function fallbackDownload(filename, contents) {
  const blob = new Blob([contents], { type: 'text/plain' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}
```

Summary: Prefer File System Access API for direct writes; use blob downloads as a robust fallback.

* * *

## 5) Handling permissions safely

File System Access API exposes permission patterns:

*   handle.queryPermission({mode: 'read' | 'readwrite'})
    
*   handle.requestPermission({mode: ...})
    

Example:

```javascript
const permission = await fileHandle.queryPermission({ mode: 'read' });
if (permission === 'prompt') {
  const granted = await fileHandle.requestPermission({ mode: 'read' });
  if (granted !== 'granted') throw new Error('Permission denied');
}
```

Notes:

*   Permissions are granted per origin and can be revoked by the user.
    
*   You can persist FileSystemHandle objects in IndexedDB using structured cloning where supported; always feature-detect before relying on that.
    

Summary: Detect permissions, request when needed, and always handle denials gracefully.

* * *

## 6) Working with large files and streams

Avoid loading huge files into memory. Use streams:

Reading:

```javascript
const stream = file.stream();
const reader = stream.getReader();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  // process Uint8Array chunk
}
```

Writing with FileSystemWritableFileStream:

```javascript
const writable = await handle.createWritable();
const encoder = new TextEncoder();
await writable.write(encoder.encode('chunked data part 1'));
await writable.close();
```

Summary: Use streaming APIs for large files to keep memory usage low and UX responsive.

* * *

## 7) UX, accessibility, and security tips

*   Always explain why you need file access in UI copy (privacy-friendly).
    
*   Show previews and read-only safe views before asking for write permissions.
    
*   Provide clear fallbacks if APIs aren't available.
    
*   Check for max file sizes and handle errors (quota, permission, aborted reads).
    
*   Make keyboard-accessible file buttons and label input elements.
    

Summary: Good UX and clear communication reduce friction and build trust.

* * *

## Browser support and progressive enhancement

Support for the File System Access API varies across browsers and versions. Instead of hard assumptions:

*   Feature-detect (e.g., 'showOpenFilePicker' in window).
    
*   Provide input\[type="file"\] / blob-download fallbacks.
    
*   Consider using small polyfills or server-side uploads when you need universal compatibility.
    

Summary: Build feature detection + graceful fallbacks; don’t rely on a single API.

* * *

## Example: small React hook for picking a file (progressive)

```javascript
import { useState } from 'react';

export function useFilePicker() {
  const [file, setFile] = useState(null);

  async function pickFile() {
    if (window.showOpenFilePicker) {
      const [handle] = await window.showOpenFilePicker();
      const file = await handle.getFile();
      setFile(file);
      return;
    }
    // fallback: click hidden input
    return new Promise((resolve) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.onchange = () => {
        const f = input.files[0];
        setFile(f);
        resolve();
      };
      input.click();
    });
  }

  return { file, pickFile };
}
```

Summary: Wrap feature detection into small hooks for clean React components.

* * *

## Conclusion — what to build next

File and folder access in web apps moved from clunky uploads to near-native workflows. Start by adding input\[type="file"\] and drag-and-drop, then progressively enable the File System Access API where available. Always feature-detect, handle permissions, and provide fallbacks.

Next step: try a small demo — implement a folder picker that lists files and a "Save changes" button that writes back using createWritable. If you want, I can scaffold that demo in React with IndexedDB persistence for handles.
