Skip to content

Theming

The picker renders its own UI, so it ships one stylesheet and themes through CSS custom properties. There's no utility framework to configure and nothing to wire up beyond a single import.

Import the stylesheet

The stylesheet is required — without it the dialog is unstyled. Import it once in your app's entry:

ts
import '@anil-labs/file-picker-core/styles.css'

Light, dark & auto

Set the theme option to 'light', 'dark' or 'auto' (the default):

ts
new FilePicker({ adapter, theme: 'auto' }) // 'light' | 'dark' | 'auto'
  • auto follows the OS via prefers-color-scheme.
  • light / dark force a mode regardless of the OS setting.

Under the hood the theme is applied as a class on the picker root: theme: 'dark' adds .fp--dark, theme: 'light' adds .fp--light, and auto adds neither (so the prefers-color-scheme rules apply). You can add those classes yourself if you theme a region of the page independently.

Switch the theme at runtime

The picker renders no theme toggle of its own — your app owns that (e.g. a switch in your navbar). Drive the picker's theme from your app by passing theme and calling setTheme():

ts
const picker = new FilePicker({ adapter, theme: 'auto' })
picker.setTheme('dark') // 'light' | 'dark' | 'auto'
picker.on('theme', (t) => console.log('theme is now', t))

The framework bindings pass theme reactively, so binding a prop is enough:

tsx
// React — the picker follows your app's theme state
<FilePicker adapter={adapter} theme={appIsDark ? 'dark' : 'light'} />

Responsive

On narrow viewports the filter bar collapses into an off-canvas drawer, opened from the toolbar's filter button, so the media grid keeps the screen. It's automatic — nothing to configure.

Customize with --fp-* variables

All colors, the corner radius and the shadow are CSS variables on the .fp root. Override them in your own stylesheet to match your brand:

css
.fp {
  --fp-accent: #7c3aed;
  --fp-accent-solid: #7c3aed;
  --fp-accent-soft: rgba(124, 58, 237, 0.12);
  --fp-radius: 16px;
}

--fp-accent and --fp-accent-solid are separate on purpose. In a dark theme they pull opposite ways: the accent has to be light to read as text or a border against a dark surface, while a fill carrying --fp-accent-fg text has to be dark enough for it. Set both to the same value on a light background; on dark, keep the accent light and the solid deep enough for its label (aim for 4.5:1).

Available variables

VariablePurposeLight default
--fp-bgDialog background#ffffff
--fp-fgPrimary text#1f2733
--fp-mutedSecondary text#6b7280
--fp-faintFaint / placeholder text#9aa4b2
--fp-borderBorders and dividers#e5e7eb
--fp-surfacePanels and inputs#f8fafc
--fp-surface-2Raised surface#eef2f7
--fp-hoverHover background#f1f5f9
--fp-accentAccent for text, borders and focus rings#2563eb
--fp-accent-solidAccent as a fill — primary button, trigger, checked box#2563eb
--fp-accent-softAccent tint (selection)rgba(37, 99, 235, 0.12)
--fp-accent-fgText on --fp-accent-solid#ffffff
--fp-dangerDestructive actions#e5484d
--fp-goodSuccess#16a34a
--fp-overlayModal backdroprgba(15, 23, 42, 0.55)
--fp-radiusBase corner radius12px
--fp-radius-smSmall radius (inputs, chips, thumbnails)max(6px, calc(var(--fp-radius) - 3px))
--fp-radius-lgLarge radius (dialog card, previews)calc(var(--fp-radius) + 4px)
--fp-card-minMinimum media-card width (grid track)148px
--fp-shadowDialog shadow0 12px 44px rgba(2, 8, 23, 0.22)
--fp-fontFont familysystem-ui, -apple-system, 'Segoe UI', Roboto, sans-serif
--fp-zBase stacking z-index (overlays layer above it)9999

--fp-radius-sm and --fp-radius-lg derive from --fp-radius, so overriding just the base radius rescales the whole corner scale together.

--fp-card-min drives the grid from tablet width up. At phone widths (≤ 520px) the grid is a fixed two columns instead, so thumbnails stay big enough to judge; override .fp-grid { grid-template-columns: … } inside your own media query if you want a different count there.

Dark mode overrides

The stylesheet supplies dark values automatically for theme: 'auto' (via prefers-color-scheme) and for .fp--dark. To tune the dark palette, override the variables under the dark selectors.

An @media block can't sit inside a comma-separated selector list, so the two dark selectors have to be written as separate rules — mirroring the shipped stylesheet. The first covers forced dark (.fp--dark), the second covers auto under an OS that prefers dark:

css
/* Forced dark: theme: 'dark' */
.fp.fp--dark {
  --fp-bg: #12151a;
  --fp-accent: #8ab4ff;
}

/* Auto, when the OS prefers dark (and dark isn't overridden to light) */
@media (prefers-color-scheme: dark) {
  .fp:not(.fp--light) {
    --fp-bg: #12151a;
    --fp-accent: #8ab4ff;
  }
}

Scope styles with className

Add a custom class to the picker root with the className option to target one instance without affecting others:

ts
new FilePicker({ adapter, className: 'brand-picker' })
css
.brand-picker {
  --fp-accent: #ef6c00;
}

Released under the MIT License.