|
# The Frontend 🌐⭐️
|
|
|
|
This guide will cover everything you need to know to implement your custom component's frontend.
|
|
|
|
Tip: Gradio components use Svelte. Writing Svelte is fun! If you're not familiar with it, we recommend checking out their interactive [guide](https://learn.svelte.dev/tutorial/welcome-to-svelte).
|
|
|
|
## The directory structure
|
|
|
|
The frontend code should have, at minimum, three files:
|
|
|
|
* `Index.svelte`: This is the main export and where your component's layout and logic should live.
|
|
* `Example.svelte`: This is where the example view of the component is defined.
|
|
|
|
Feel free to add additional files and subdirectories.
|
|
If you want to export any additional modules, remember to modify the `package.json` file
|
|
|
|
```json
|
|
"exports": {
|
|
".": "./Index.svelte",
|
|
"./example": "./Example.svelte",
|
|
"./package.json": "./package.json"
|
|
},
|
|
```
|
|
|
|
## The Index.svelte file
|
|
|
|
Your component should expose the following props that will be passed down from the parent Gradio application.
|
|
|
|
```typescript
|
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
import type { Gradio } from "@gradio/utils";
|
|
|
|
export let gradio: Gradio<{
|
|
event_1: never;
|
|
event_2: never;
|
|
}>;
|
|
|
|
export let elem_id = "";
|
|
export let elem_classes: string[] = [];
|
|
export let scale: number | null = null;
|
|
export let min_width: number | undefined = undefined;
|
|
export let loading_status: LoadingStatus | undefined = undefined;
|
|
export let mode: "static" | "interactive";
|
|
```
|
|
|
|
* `elem_id` and `elem_classes` allow Gradio app developers to target your component with custom CSS and JavaScript from the Python `Blocks` class.
|
|
|
|
* `scale` and `min_width` allow Gradio app developers to control how much space your component takes up in the UI.
|
|
|
|
* `loading_status` is used to display a loading status over the component when it is the output of an event.
|
|
|
|
* `mode` is how the parent Gradio app tells your component whether the `interactive` or `static` version should be displayed.
|
|
|
|
* `gradio`: The `gradio` object is created by the parent Gradio app. It stores some application-level configuration that will be useful in your component, like internationalization. You must use it to dispatch events from your component.
|
|
|
|
A minimal `Index.svelte` file would look like:
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
import { Block } from "@gradio/atoms";
|
|
import { StatusTracker } from "@gradio/statustracker";
|
|
import type { Gradio } from "@gradio/utils";
|
|
|
|
export let gradio: Gradio<{
|
|
event_1: never;
|
|
event_2: never;
|
|
}>;
|
|
|
|
export let value = "";
|
|
export let elem_id = "";
|
|
export let elem_classes: string[] = [];
|
|
export let scale: number | null = null;
|
|
export let min_width: number | undefined = undefined;
|
|
export let loading_status: LoadingStatus | undefined = undefined;
|
|
export let mode: "static" | "interactive";
|
|
</script>
|
|
|
|
<Block
|
|
visible={true}
|
|
{elem_id}
|
|
{elem_classes}
|
|
{scale}
|
|
{min_width}
|
|
allow_overflow={false}
|
|
padding={true}
|
|
>
|
|
{#if loading_status}
|
|
<StatusTracker
|
|
autoscroll={gradio.autoscroll}
|
|
i18n={gradio.i18n}
|
|
{...loading_status}
|
|
/>
|
|
{/if}
|
|
<p>{value}</p>
|
|
</Block>
|
|
```
|
|
|
|
## The Example.svelte file
|
|
|
|
The `Example.svelte` file should expose the following props:
|
|
|
|
```typescript
|
|
export let value: string;
|
|
export let type: "gallery" | "table";
|
|
export let selected = false;
|
|
export let index: number;
|
|
```
|
|
|
|
* `value`: The example value that should be displayed.
|
|
|
|
* `type`: This is a variable that can be either `"gallery"` or `"table"` depending on how the examples are displayed. The `"gallery"` form is used when the examples correspond to a single input component, while the `"table"` form is used when a user has multiple input components, and the examples need to populate all of them.
|
|
|
|
* `selected`: You can also adjust how the examples are displayed if a user "selects" a particular example by using the selected variable.
|
|
|
|
* `index`: The current index of the selected value.
|
|
|
|
* Any additional props your "non-example" component takes!
|
|
|
|
This is the `Example.svelte` file for the code `Radio` component:
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
export let value: string;
|
|
export let type: "gallery" | "table";
|
|
export let selected = false;
|
|
</script>
|
|
|
|
<div
|
|
class:table={type === "table"}
|
|
class:gallery={type === "gallery"}
|
|
class:selected
|
|
>
|
|
{value}
|
|
</div>
|
|
|
|
<style>
|
|
.gallery {
|
|
padding: var(--size-1) var(--size-2);
|
|
}
|
|
</style>
|
|
```
|
|
|
|
## Handling Files
|
|
|
|
If your component deals with files, these files **should** be uploaded to the backend server.
|
|
The `@gradio/client` npm package provides the `upload` and `prepare_files` utility functions to help you do this.
|
|
|
|
The `prepare_files` function will convert the browser's `File` datatype to gradio's internal `FileData` type.
|
|
You should use the `FileData` data in your component to keep track of uploaded files.
|
|
|
|
The `upload` function will upload an array of `FileData` values to the server.
|
|
|
|
Here's an example of loading files from an `<input>` element when its value changes.
|
|
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import { upload, prepare_files, type FileData } from "@gradio/client";
|
|
export let root;
|
|
export let value;
|
|
let uploaded_files;
|
|
|
|
async function handle_upload(file_data: FileData[]): Promise<void> {
|
|
await tick();
|
|
uploaded_files = await upload(file_data, root);
|
|
}
|
|
|
|
async function loadFiles(files: FileList): Promise<void> {
|
|
let _files: File[] = Array.from(files);
|
|
if (!files.length) {
|
|
return;
|
|
}
|
|
if (file_count === "single") {
|
|
_files = [files[0]];
|
|
}
|
|
let file_data = await prepare_files(_files);
|
|
await handle_upload(file_data);
|
|
}
|
|
|
|
async function loadFilesFromUpload(e: Event): Promise<void> {
|
|
const target = e.target;
|
|
|
|
if (!target.files) return;
|
|
await loadFiles(target.files);
|
|
}
|
|
</script>
|
|
|
|
<input
|
|
type="file"
|
|
on:change={loadFilesFromUpload}
|
|
multiple={true}
|
|
/>
|
|
```
|
|
|
|
The component exposes a prop named `root`.
|
|
This is passed down by the parent gradio app and it represents the base url that the files will be uploaded to and fetched from.
|
|
|
|
For WASM support, you should get the upload function from the `Context` and pass that as the third parameter of the `upload` function.
|
|
|
|
```typescript
|
|
<script lang="ts">
|
|
import { getContext } from "svelte";
|
|
const upload_fn = getContext<typeof upload_files>("upload_files");
|
|
|
|
async function handle_upload(file_data: FileData[]): Promise<void> {
|
|
await tick();
|
|
await upload(file_data, root, upload_fn);
|
|
}
|
|
</script>
|
|
```
|
|
|
|
## Leveraging Existing Gradio Components
|
|
|
|
Most of Gradio's frontend components are published on [npm](https://www.npmjs.com/), the javascript package repository.
|
|
This means that you can use them to save yourself time while incorporating common patterns in your component, like uploading files.
|
|
For example, the `@gradio/upload` package has `Upload` and `ModifyUpload` components for properly uploading files to the Gradio server.
|
|
Here is how you can use them to create a user interface to upload and display PDF files.
|
|
|
|
```svelte
|
|
<script>
|
|
import { type FileData, Upload, ModifyUpload } from "@gradio/upload";
|
|
import { Empty, UploadText, BlockLabel } from "@gradio/atoms";
|
|
</script>
|
|
|
|
<BlockLabel Icon={File} label={label || "PDF"} />
|
|
{#if value === null && interactive}
|
|
<Upload
|
|
filetype="application/pdf"
|
|
on:load={handle_load}
|
|
{root}
|
|
>
|
|
<UploadText type="file" i18n={gradio.i18n} />
|
|
</Upload>
|
|
{:else if value !== null}
|
|
{#if interactive}
|
|
<ModifyUpload i18n={gradio.i18n} on:clear={handle_clear}/>
|
|
{/if}
|
|
<iframe title={value.orig_name || "PDF"} src={value.data} height="{height}px" width="100%"></iframe>
|
|
{:else}
|
|
<Empty size="large"> <File/> </Empty>
|
|
{/if}
|
|
```
|
|
|
|
You can also combine existing Gradio components to create entirely unique experiences.
|
|
Like rendering a gallery of chatbot conversations.
|
|
The possibilities are endless, please read the documentation on our javascript packages [here](https://gradio.app/main/docs/js).
|
|
We'll be adding more packages and documentation over the coming weeks!
|
|
|
|
## Matching Gradio Core's Design System
|
|
|
|
You can explore our component library via Storybook. You'll be able to interact with our components and see them in their various states.
|
|
|
|
For those interested in design customization, we provide the CSS variables consisting of our color palette, radii, spacing, and the icons we use - so you can easily match up your custom component with the style of our core components. This Storybook will be regularly updated with any new additions or changes.
|
|
|
|
[Storybook Link](https://gradio.app/main/docs/js/storybook)
|
|
|
|
## Custom configuration
|
|
|
|
If you want to make use of the vast vite ecosystem, you can use the `gradio.config.js` file to configure your component's build process. This allows you to make use of tools like tailwindcss, mdsvex, and more.
|
|
|
|
Currently, it is possible to configure the following:
|
|
|
|
Vite options:
|
|
- `plugins`: A list of vite plugins to use.
|
|
|
|
Svelte options:
|
|
- `preprocess`: A list of svelte preprocessors to use.
|
|
- `extensions`: A list of file extensions to compile to `.svelte` files.
|
|
- `build.target`: The target to build for, this may be necessary to support newer javascript features. See the [esbuild docs](https://esbuild.github.io/api/#target) for more information.
|
|
|
|
The `gradio.config.js` file should be placed in the root of your component's `frontend` directory. A default config file is created for you when you create a new component. But you can also create your own config file, if one doesn't exist, and use it to customize your component's build process.
|
|
|
|
### Example for a Vite plugin
|
|
|
|
Custom components can use Vite plugins to customize the build process. Check out the [Vite Docs](https://vitejs.dev/guide/using-plugins.html) for more information.
|
|
|
|
Here we configure [TailwindCSS](https://tailwindcss.com), a utility-first CSS framework. Setup is easiest using the version 4 prerelease.
|
|
|
|
```
|
|
npm install tailwindcss@next @tailwindcss/vite@next
|
|
```
|
|
|
|
In `gradio.config.js`:
|
|
|
|
```typescript
|
|
import tailwindcss from "@tailwindcss/vite";
|
|
export default {
|
|
plugins: [tailwindcss()]
|
|
};
|
|
```
|
|
|
|
Then create a `style.css` file with the following content:
|
|
|
|
```css
|
|
@import "tailwindcss";
|
|
```
|
|
|
|
Import this file into `Index.svelte`. Note, that you need to import the css file containing `@import` and cannot just use a `<style>` tag and use `@import` there.
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
[...]
|
|
import "./style.css";
|
|
[...]
|
|
</script>
|
|
```
|
|
|
|
### Example for Svelte options
|
|
|
|
In `gradio.config.js` you can also specify a some Svelte options to apply to the Svelte compilation. In this example we will add support for [`mdsvex`](https://mdsvex.pngwn.io), a Markdown preprocessor for Svelte.
|
|
|
|
In order to do this we will need to add a [Svelte Preprocessor](https://svelte.dev/docs/svelte-compiler#preprocess) to the `svelte` object in `gradio.config.js` and configure the [`extensions`](https://github.com/sveltejs/vite-plugin-svelte/blob/HEAD/docs/config.md#config-file) field. Other options are not currently supported.
|
|
|
|
First, install the `mdsvex` plugin:
|
|
|
|
```bash
|
|
npm install mdsvex
|
|
```
|
|
|
|
Then add the following to `gradio.config.js`:
|
|
|
|
```typescript
|
|
import { mdsvex } from "mdsvex";
|
|
|
|
export default {
|
|
svelte: {
|
|
preprocess: [
|
|
mdsvex()
|
|
],
|
|
extensions: [".svelte", ".svx"]
|
|
}
|
|
};
|
|
```
|
|
|
|
Now we can create `mdsvex` documents in our component's `frontend` directory and they will be compiled to `.svelte` files.
|
|
|
|
```md
|
|
<!-- HelloWorld.svx -->
|
|
|
|
<script lang="ts">
|
|
import { Block } from "@gradio/atoms";
|
|
|
|
export let title = "Hello World";
|
|
</script>
|
|
|
|
<Block label="Hello World">
|
|
|
|
# {title}
|
|
|
|
This is a markdown file.
|
|
|
|
</Block>
|
|
```
|
|
|
|
We can then use the `HelloWorld.svx` file in our components:
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import HelloWorld from "./HelloWorld.svx";
|
|
</script>
|
|
|
|
<HelloWorld />
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
You now how to create delightful frontends for your components!
|
|
|
|
|