Rendering the Filestack Picker Inside Your Own Container Element

Posted on | Last updated on
Rendering the Filestack Picker Inside Your Own Container Element

Rendering the Filestack picker inside your own container element is a single child prop. Every picker component in v7 accepts one child, clones it, sets the generated DOM id on it, and mounts the picker inside. Your border, height, radius and shadow survive.

The custom container example in filestack-snippets is the version this is drawn from.

Key takeaways

  • Pass one empty child element and the picker mounts inside it, keeping your styles.
  • The child must spread its props onto a real DOM element, or the id is dropped and nothing renders.
  • Give the container an explicit height, since a collapsed box renders empty too.
  • You style the container; the picker’s interior is configuration, not CSS.
  • Wrap it with useResolvedPickerProps so the wrapper still respects FilestackProvider.

The default, and why you would replace it

Both packages are needed, since v7 takes filestack-js as a peer dependency:

npm install filestack-react@^7.0.1 filestack-js@^4.0.1

With no child, PickerInline renders its own container, a plain div 500 pixels tall. PickerOverlay and PickerDropPane do the same with their own defaults. That is fine until it sits inside a design system, at which point the picker is the one element on the page with no border radius and the wrong height.

<PickerInline onUploadDone={handleDone}>
  <div
    style={{
      height: 420,
      border: '1px solid #d0d0d0',
      borderRadius: 12,
      overflow: 'hidden',
      boxShadow: '0 1px 4px rgba(0,0,0,0.08)',
    }}
  />
</PickerInline>

The child is empty on purpose. You are supplying a container, not content, and anything inside it would be replaced when the picker mounts.

The picker mounted inside a custom container keeping its border radius and shadow
The picker mounted inside a custom container keeping its border radius and shadow

 

How the cloning works

The component calls cloneElement on your child and sets an id on it. The picker is then initialised against that id.

Two consequences follow.

The child must accept an id prop and put it on a real DOM element. A plain div does. A component of your own does only if it spreads its props onto the element it renders:

// works, the id reaches the div
const Panel = (props) => <div {...props} className="panel" />;

// does not, the id is dropped
const FixedPanel = () => <div className="panel" />;

When the id is dropped, the picker has nothing to mount into and nothing appears, with no error to explain it. If a custom container renders empty, this is the first thing to check.

Only one child. The prop takes a single element, not an array and not a fragment containing several. Wrap what you need in one element.

What you can and cannot style this way

The container is yours. The interior belongs to the picker.

That means height, width, borders, radius, shadow, margin, background behind the picker and anything positional are all under your control, and they are usually the properties that matter for fitting into a layout.

The picker’s internal typography, buttons, source list and colours are not reachable from the container. If those need to change, that is a picker configuration question rather than a CSS one, and pickerOptions carries the customisations the picker supports, including customText for wording and displayMode for arrangement.

Styling into the picker’s internals with descendant selectors works until a release changes a class name, and then it breaks in production on a schedule you do not control.

Overflow and the border radius

The same class of styling problem on plain inputs is worked through in the Bootstrap file upload styling guide.

overflow: hidden on the container is what makes a border radius visible. Without it the picker renders square corners inside your rounded box.

The same applies to a container with a shadow and no radius, where the picker’s own edges sit flush against the boundary.

Sizing

Give the container an explicit height. The picker fills its container, and a container with no height collapses to nothing, which produces the same empty result as a dropped id but for a different reason.

Percentage heights work when the parent has a height. In a flex column, flex: 1 on the container with a fixed height on the parent is more reliable than percentages, and it survives a viewport change better.

For a responsive layout the useful move is to change the component rather than the container. An inline picker at 420 pixels on a desktop is reasonable and on a phone occupies most of the screen, so switching to the overlay at a breakpoint tends to beat shrinking the container.

Join the Filestack developer community on Discord

Applying it to the other components

The prop behaves identically across all three, which makes the pattern portable:

<PickerDropPane onUploadDone={handleDone}>
  <div className="dropzone" />
</PickerDropPane>

On PickerDropPane this is the common case rather than the exception, since the component renders almost nothing of its own and the drop area’s appearance is entirely the container’s job. A dashed border, a hover state and a label are usually all it needs.

On PickerOverlay a custom container is rarer, because the modal positions itself and the element you supply sits inside that. Reach for it when the modal needs a fixed width in a design that would otherwise let it fill the viewport.

Wrapping it into a reusable component

The props the wrapper needs to forward are listed on the React file upload SDK page.

Once a container is styled the way your design system wants, wrap it. Two details keep the wrapper consistent with the built-in components.

import { PickerInline, useResolvedPickerProps } from 'filestack-react';
import type { PickerBaseProps } from 'filestack-react';

export function UploadPanel(props: PickerBaseProps) {
  const resolved = useResolvedPickerProps(props);

  return (
    <PickerInline {...resolved}>
      <div className="upload-panel" />
    </PickerInline>
  );
}

Typing the props as PickerBaseProps means your wrapper accepts exactly what a picker accepts, so it keeps working when the SDK adds an option. Running them through useResolvedPickerProps means the wrapper follows the same precedence rules the built-in components follow, merging with whatever FilestackProvider supplies rather than overriding it.

A wrapper that skips the hook ignores every value FilestackProvider supplies.

The container stays inside the wrapper. That is the point of building one, since the whole reason to wrap is so no other file has to remember the height and the overflow rule.

Debugging an empty container

A custom container that renders nothing has a short list of causes. Work through them in order.

The id was dropped, because the child is a component that does not spread its props. Render the child alone and inspect it in the browser to confirm an id attribute is present.

The container has no height, so the picker mounted into a zero-pixel box. Give it a fixed height temporarily to rule this in or out.

More than one child was passed. The prop takes a single element, and a fragment wrapping two siblings does not satisfy that.

The picker is outside a provider and has no apikey prop, in which case nothing was going to render regardless of the container. Test with a bare picker and no child first, which separates a container problem from a configuration one.

Accessibility

The container is a real element in your tree, so everything you would normally do to an element applies to it. A labelled region around a drop pane, a visible focus style on an interactive container, and a heading above it all behave exactly as they do elsewhere in your design system.

Announcing the region is worth doing first. A heading immediately above the container, or an aria-label on it, tells a screen reader user what the region is before they reach the picker’s controls, and it costs a line.

Progress and error states are the other half of this, and file upload accessibility with WCAG and ARIA covers them in React specifically.

After the file lands

Styling the container changes nothing about the result. Each file returns a handle, and the thumbnail you render inside your own layout is a transformation on that handle rather than a second component. The guide to chaining image transformations works through stacking several of them into one URL.

FAQ

Why does my custom container render nothing?

Usually the child is a component that does not spread its props, so the generated id never reaches a DOM element and the picker has nothing to mount into. A collapsed container with no height produces the same empty result for a different reason.

Can I restyle the picker’s buttons and colours this way?

No. The container is yours and the interior belongs to the picker. Wording and arrangement are configuration through customText and displayMode, and reaching into the internals with descendant selectors breaks whenever a class name changes.

Why are my rounded corners square inside the container?

overflow: hidden is missing. Without it the picker renders its own square edges inside your rounded box, and the same flush-edge problem shows up on a container with a shadow.

Do I need useResolvedPickerProps in a wrapper?

Yes, if a FilestackProvider is anywhere above it. The hook applies the same precedence rules the built-in components use, and a wrapper that skips it ignores every value the provider supplies.

 

 

Read More →