Theming

Our prebuilt components are customizable so you can make them fit with the theme of your application.

UploadButton Anatomy

Simplified component structure:

<div className={className} data-state={}>
  <label
    data-ut-element="button"
    data-state={/* "ready" | "readying" | "uploading" */}
  >
    <input />
    /button content goes here/
  </label>
  <div
    data-ut-element="allowed-content"
    data-state={/* "ready" | "readying" | "uploading" */}
  >
    /allowed content text goes here/
  </div>
</div>
UploadButton anatomy

UploadDropzone Anatomy

Simplified component structure:

<div className={className} data-state={/* "ready" | "readying" | "uploading" */}>
  <svg data-ut-element="upload-icon" data-state={/* "ready" | "readying" | "uploading" */}>
    ...
  </svg>
  <label data-ut-element="label" data-state={/* "ready" | "readying" | "uploading" */}>
    {/* label content goes here */}
    <input disabled={!ready} />
  </label>
  <div data-ut-element="allowed-content" data-state={/* "ready" | "readying" | "uploading" */}>
    /allowed content goes here/
  </div>
  <button
    data-ut-element="button"
    data-state={/* "ready" | "readying" | "uploading" */}
    disabled={isUploading}
  >
    {/* button content goes here */}
  </button>
</div>
UploadDropzone anatomy

Theming props

className

Both UploadButton and UploadDropzone accept a className prop. It allows you to pass any additional classes to the component. All classes that are being passed through this prop are going to be applied to the outermost element - container.

appearance

Both UploadButton and UploadDropzone accept an appearance prop. It accepts an object with keys that correspond to elements of a component. The interfaces for the appearance prop for UploadButton and UploadDropzone are:

type StyleField =
  | string
  | CSSProperties
  | ((args: CallbackArguments) => string | CSSProperties);

type UploadButtonProps = {
  /* rest of props */
  appearance?: {
    container?: StyleField;
    button?: StyleField;
    allowedContent?: StyleField;
  };
};

type UploadDropzoneProps = {
  /* rest of props */
  appearance?: {
    container?: StyleField;
    uploadIcon?: StyleField;
    label?: StyleField;
    allowedContent?: StyleField;
    button?: StyleField;
  };
};

where the CallbackArguments is defined as (depending on the component):

type ButtonCallbackArguments = {
  ready: boolean;
  isUploading: boolean;
  uploadProgress: number;
  fileTypes: string[];
  files: File[];
};

type DropzoneCallbackArguments = {
  ready: boolean;
  isUploading: boolean;
  uploadProgress: number;
  fileTypes: string[];
  isDragActive: boolean;
  files: File[];
};

Theming with TailwindCSS

Configuring TailwindCSS

To leverage the best developer experience, we strongly recommend wrapping your Tailwind config with our utility function withUt. This utility function adds additional classes and variants used to style our components.

In addition, it also automatically sets the content option to include all the necessary classes that the components use. This allows you to avoid having duplicated styles in your bundle. Therefore, when using withUt, you should not import our stylesheet into your app. If you choose not to use withUt, you have to import the default stylesheet to make the components look right.

import { withUt } from "uploadthing/tw";

export default withUt({
  // your config goes here
});

Style using the className prop

className accepts any classes and will merge them using tailwind-merge, meaning you can pass any class you like and it will be applied correctly, overriding the default styles if necessary.

The withUt wrapper adds custom variants that you can leverage to easily target different elements of the component and its state:

VariantDescription
ut-button:Applied to the button element.
ut-allowed-content:Applied to the allowed content element.
ut-label:Applied to the label element.
ut-upload-icon:Applied to the upload icon element.
ut-readying:Applied to the container element when the component is readying.
ut-ready:Applied to the container element when the component is ready.
ut-uploading:Applied to the container element when the component is uploading.

These variants and classes can be used in conjunction with each other to make component match your design in the exact way you want.

<UploadButton
    className="mt-4 ut-button:bg-red-500 ut-button:ut-readying:bg-red-500/50"
               |    └─ applied to the button └─ applied to the button when readying
               └─ applied to the container
/>
Allowed content
Allowed content
<UploadDropzone
    className="bg-slate-800 ut-label:text-lg ut-allowed-content:ut-uploading:text-red-300"
               |            |                └─ applied to the allowed content when uploading
               |            └─ applied to the label
               └─ applied to the container

/>

Style using the appearance prop

If you're not using the withUt wrapper, or prefer splitting your styles up a bit, you can use the appearance prop to target the different elements of the component.

<UploadButton
  appearance={{
    button:
      "ut-ready:bg-green-500 ut-uploading:cursor-not-allowed rounded-r-none bg-red-500 bg-none after:bg-orange-400",
    container: "w-max flex-row rounded-md border-cyan-300 bg-slate-800",
    allowedContent:
      "flex h-8 flex-col items-center justify-center px-2 text-white",
  }}
/>
Allowed content
Allowed content

Theming with custom classes

className prop

className prop accepts any classes so you can pass there anything you like. When it comes to custom classes, you can use data attributes to target specific elements of components.

<UploadButton className="custom-class" />
/* applied to container */
.custom-class {
  background-color: none;
}

/* applied to button */
.custom-class > *[data-ut-element="button"] {
  font-size: 1.6rem;
  color: rgb(0 0 0 / 1);
  background-color: rgb(239 68 68 / 1);
}

/* applied to button when uploading */
.custom-class > *[data-ut-element="button"][data-state="readying"] {
  background-color: rgb(239 68 68 / 0.5);
  color: rgb(0 0 0 / 0.5);
  cursor: not-allowed;
}

/* applied to the button when uploading */
.custom-class > *[data-ut-element="button"][data-state="uploading"] {
  background-color: rgb(239 68 68 / 0.5);
  color: rgb(0 0 0 / 0.5);
  cursor: not-allowed;
}

/* applied to the upload indicator when uploading */
.custom-class > *[data-ut-element="button"][data-state="uploading"]::after {
  background-color: rgb(234 88 12 / 1);
}
Allowed content
Allowed content
Allowed content

appearance prop

If you need, you can pass classes directly to specific elements of components or provide a callback that will be called with the current state of the component and will return a string

<UploadButton
  appearance={{
    button({ ready, isUploading }) {
      return `custom-button ${
        ready ? "custom-button-ready" : "custom-button-not-ready"
      } ${isUploading ? "custom-button-uploading" : ""}`;
    },
    container: "custom-container",
    allowedContent: "custom-allowed-content",
  }}
/>
/* applied to container */
.custom-container {
  background-color: none;
  margin-top: 1rem;
}

/* applied to container when readying */
.custom-container[data-state="readying"] {
  background-color: none;
}

/* applied to button */
.custom-button {
  font-size: 1.6rem;
  color: rgb(0 0 0 / 1);
  background-color: rgb(239 68 68 / 1);
}

/* applied to button when uploading */
.custom-button-uploading {
  background-color: rgb(239 68 68 / 0.5);
  color: rgb(0 0 0 / 0.5);
  cursor: not-allowed;
}

.custom-button-uploading::after {
  background-color: rgb(234 88 12 / 1) !important;
}

/* applied to the button when ready */
.custom-button-ready {
  color: #ecfdf5;
}

/* applied to the button when not ready */
.custom-button-not-ready {
  background-color: rgb(239 68 68 / 0.5);
  color: rgb(0 0 0 / 0.5);
  cursor: not-allowed;
}
Allowed content
Allowed content
Allowed content

Theming with inline styles

appearance prop

If you need, you can pass inline styles directly to specific elements of component or provide a callback that will be called with the current state of the component and will return a CSSProperties object

<UploadButton
  appearance={{
    button({ ready, isUploading }) {
      return {
        fontSize: "1.6rem",
        color: "black",
        ...(ready && { color: "#ecfdf5" }),
        ...(isUploading && { color: "#d1d5db" }),
      };
    },
    container: {
      marginTop: "1rem",
    },
    allowedContent: {
      color: "#a1a1aa",
    },
  }}
/>
Allowed content
Allowed content
Allowed content

Content customization

To customize the content of UploadButton and UploadDropzone, you can use the content prop that accepts an object with the following shape:

ReactNode in the type definitions below will be the equivalent depending on the framework you use, e.g. JSX.Element in Solid.js.

type ContentField = ReactNode | ((args: CallbackArguments) => ReactNode);

type UploadButtonProps = {
  /* rest of props */
  content?: {
    button?: ContentField;
    allowedContent?: ContentField;
  };
};

type UploadDropzoneProps = {
  /* rest of props */
  content?: {
    uploadIcon?: ContentField;
    label?: ContentField;
    allowedContent?: ContentField;
    button?: ContentField;
  };
};

Example

<UploadButton
  endpoint="mockRoute"
  content={{
    button({ ready }) {
      if (ready) return <div>Upload stuff</div>;

      return "Getting ready...";
    },
    allowedContent({ ready, fileTypes, isUploading }) {
      if (!ready) return "Checking what you allow";
      if (isUploading) return "Seems like stuff is uploading";
      return `Stuff you can upload: ${fileTypes.join(", ")}`;
    },
  }}
/>
Checking what you allow
Stuff you can upload:
Seems like stuff is uploading

Was this page helpful?