Custom UI
You can display custom user interface (UI) JSX components using the
@metamask/snaps-sdk
module when
implementing the following features:
JSX is supported in the MetaMask extension and Flask version 12 and later. New UI components will be added as JSX components. The previous function-based library is deprecated.
To use custom UI, first install @metamask/snaps-sdk
using the following command:
yarn add @metamask/snaps-sdk
Then, whenever you're required to return a custom UI component, import the components from the
SDK at @metamask/snaps-sdk/jsx
and build your UI with them.
For example, to display a Box
using
snap_dialog
:
import { Box, Heading, Text } from "@metamask/snaps-sdk/jsx";
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: (
<Box>
<Heading>Alert heading</Heading>
<Text>Something happened in the system.</Text>
</Box>
),
},
});
JSX can only be used in .jsx
or .tsx
files.
Components
The following custom UI components are available:
Address
Outputs a formatted text field for a blockchain address. The address is automatically displayed with a Jazzicon and truncated value. Hovering over the address shows the full value in a tooltip.
Props
address
:string
- A valid Ethereum address, starting with0x
, or a valid CAIP-10 address.
Example
- Ethereum address
- CAIP-10 address
import { Box, Heading, Address } from "@metamask/snaps-sdk/jsx";
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: (
<Box>
<Heading>Are you sure you want to send tokens to this address?</Heading>
<Address address="0x000000000000000000000000000000000000dEaD" />
</Box>
),
},
});
import { Box, Heading, Address } from "@metamask/snaps-sdk/jsx";
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: (
<Box>
<Heading>The following is an Ethereum address</Heading>
<Address address="eip155:1:0x1234567890123456789012345678901234567890" />
<Heading>The following is a Bitcoin address</Heading>
<Address address="bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6" />
</Box>
),
},
});
Avatar
Outputs a Jazzicon for an address.
MetaMask automatically calculates checksums for EVM addresses (eip155:
).
Addresses for other namespaces are not validated; you should validate them in your Snap.
Props
address
:string
- A valid CAIP-10 address.
Example
export const onHomePage: OnHomePageHandler = async () => {
return {
content: (
<Box>
<Avatar address="eip155:1:0x1234567890123456789012345678901234567890" />
<Avatar address="bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6" />
</Box>
),
};
};
Bold
Outputs bold text.
Example
import { Box, Heading, Text, Bold } from "@metamask/snaps-sdk/jsx";
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: (
<Box>
<Heading>Hello world!</Heading>
<Text>
This is <Bold>bold</Bold>.
</Text>
</Box>
),
},
});
Box
Outputs a box, which can be used as a container for other components.
Props
direction
- (Optional) The direction in which elements flow inside the box. Possible values are"horizontal"
and"vertical"
. The default is"vertical"
.alignment
- (Optional) The alignment of the elements inside the box. Possible values are"start"
,"center"
,"end"
,"space-between"
, and"space-around"
. The default is"start"
.
Example
import { Box, Heading, Text } from "@metamask/snaps-sdk/jsx";
module.exports.onHomePage = async () => {
return {
content: (
<Box>
<Heading>Features</Heading>
<Box
direction="horizontal"
alignment="space-around"
>
<Text>Feature 1</Text>
<Text>Feature 2</Text>
<Text>Feature 3</Text>
</Box>
</Box>
),
};
};
Button
Outputs a button that the user can select. For use in interactive UI.
Props
children
- The contents of the button. This can be text, anImage
component, or anIcon
component.type
- (Optional) The type of button. Possible values are"button"
and"submit"
. The default is"button"
.name
:string
- (Optional) The name that will be sent toonUserInput
when a user selects the button.variant
- (Optional) Determines the appearance of the button. Possible values are"primary"
and"destructive"
. The default is"primary"
.
Example
import { Box, Heading, Button } from "@metamask/snaps-sdk/jsx";
const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: (
<Box>
<Heading>Interactive interface</Heading>
<Button name="interactive-button">Click me</Button>
</Box>
),
},
});
await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
Card
Outputs a card component which is used to display values in a card structure.
Unlike many Card
components from other UI libraries, the Snaps Card
does not have any shape.
It is only used for layout. To give a shape to a Card
, wrap it in a Section
component.
Props
title
:Array<string | Address>
- The title of the card, can be a string or anAddress
.value
:string
- The value, shown on the right side.image
:string
- (Optional) An image shown on the left side. Accepts inline SVG.description
:string
- (Optional) A description, shown below the title.extra
:string
- (Optional) Additional text shown below the value.
Example
import icon from "./../img/icon.svg"
export const onHomePage: OnHomePageHandler = async () => {
return {
content: (
<Box>
<Card
image={icon}
title="Card title"
description="Card description"
value="Card value"
extra="Extra value"
/>
<Card
title="Minimal card"
value="Example value"
/>
<Section>
<Card
image={icon}
title="Card title"
description="Card description"
value="Card value"
extra="Extra value"
/>
<Card
title="Minimal card"
value="Example value"
/>
</Section>
</Box>
),
};
};
Checkbox
Outputs a checkbox for use in interactive UI.
Props
name
:string
- The name sent toonUserInput
.checked
:boolean
- (Optional) Whether the checkbox is checked.label
:string
- (Optional) The label for the checkbox.variant
- (Optional) The variant of the checkbox. Possible values are"default"
and"toggle"
. The default is"default"
.
Example
import { Checkbox } from "@metamask/snaps-sdk/jsx";
const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: (
<Box>
<Checkbox name="accept-terms" label="I understand the terms" />
<Checkbox name="dark-mode" label="Dark mode" variant="toggle" />
</Box>
),
},
});
Copyable
Outputs a read-only text field with a copy-to-clipboard shortcut.
Props
value
:string
- The value to copy when the user clicks on the copyable element.sensitive
:boolean
- (Optional) Indicates whether the value is sensitive. Iftrue
, the value will be hidden when the user is not interacting with the copyable element.
Example
import { Box, Text, Copyable } from "@metamask/snaps-sdk/jsx";
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: (
<Box>
<Text>Your address:</Text>
<Copyable value="0x000000000000000000000000000000000000dEaD" />
</Box>
),
},
});
Divider
Outputs a horizontal divider.
Example
import { Box, Heading, Divider, Text } from "@metamask/snaps-sdk/jsx";
module.exports.onHomePage = async () => {
return {
content: (
<Box>
<Heading>Hello world!</Heading>
<Divider />
<Text>Welcome to my Snap home page!</Text>
</Box>
),
};
};
Dropdown
Outputs a dropdown for use in interactive UI.
Props
name
:string
- The name sent toonUserInput
.children
:Option[]
- One or moreOption
components with the following props:value
:string
- The value sent toonUserInput
.children
:string
- The text displayed in the dropdown for that option.
Example
import { Box, Text, Dropdown } from "@metamask/snaps-sdk/jsx";
const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: (
<Box>
<Text>Pick a currency</Text>
<Dropdown name="currency">
<Option value="ETH">ETH</Option>
<Option value="USD">USD</Option>
</Dropdown>
</Box>
),
},
});
await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
Field
Outputs a form field, wrapping an element to give it a label and optional error.
Props
label
:string
- The label for the wrapped element.error
:string
- (Optional) Any error for the wrapped element. Setting this changes the style of the wrapped element to show that there is an error.children
- The element to be wrapped. This can be aDropdown
,Input
,Selector
, orRadioGroup
component.
Example
import { Field, Form, Input, Button } from "@metamask/snaps-sdk/jsx";
const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: (
<Form name="form-to-fill">
<Field label="First Name">
<Input name="firstName" placeholder="Enter your first name" />
</Field>
<Button type="submit">Submit</Button>
</Form>
),
},
});
await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
FileInput
Outputs a file input component for use in interactive UI.
Props
name
:string
- The name that will be sent toonUserInput
when a user interacts with the form.accept
:string[]
- (Optional) The file types that the file input field accepts. If not specified, the file input field accepts all file types. For examples of valid values, see the MDN documentation.compact
:boolean
- (Optional) Whether the file input field is compact.
Example
import { FileInput } from "@metamask/snaps-sdk/jsx";
export const onHomePage = async () => {
const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: (
<Box>
<Heading>File Upload</Heading>
<Form name="file-upload-form">
<Field>
<FileInput name="file-input" />
</Field>
<Button name="submit-file-upload-form" type="submit">
Submit
</Button>
</Form>
</Box>
),
},
});
return {
id: interfaceId,
}
};
export const onUserInput = async ({ id, event }) => {
if (event.type === UserInputEventType.FileUploadEvent && event.file !== null) {
console.log(event.file);
}
};
Form
Outputs a form for use in interactive UI.
Props
name
:string
- The name that will be sent toonUserInput
when a user interacts with the form.children
:array
- An array ofInput
orButton
components.
Example
import { Form, Input, Button } from "@metamask/snaps-sdk/jsx";
const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: (
<Form name="form-to-fill">
<Field label="First Name">
<Input name="firstName" placeholder="Enter your first name" />
</Field>
<Button type="submit">Submit</Button>
</Form>
),
},
});
await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
Heading
Outputs a heading.
This is useful for Box
titles.
Props
size
:string
- (Optional) The size of the heading. Possible values are"sm"
,"md"
, and"lg"
. The default is"sm"
.
Example
import { Box, Heading, Text } from "@metamask/snaps-sdk/jsx";
module.exports.onHomePage = async () => {
return {
content: (
<Box>
<Heading>Hello world!</Heading>
<Text>Welcome to my Snap home page!</Text>
</Box>
),
};
};
Icon
Outputs an icon.
Props
name
- The name of the icon. Possible values include"confirmation"
,"search"
, "warning
", and"menu"
. See the full list of possiblename
values.color
- (Optional) The color of the icon. Possible values are"default"
,"primary"
, and"muted"
. The default is"default"
.size
- (Optional) The size of the icon. Possible values are"md"
and"inherit"
. The default is"md"
.
Example
import { Icon } from "@metamask/snaps-sdk/jsx";
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: (
<Box direction="horizontal">
<Icon name="warning" size="md" />
<Text>Double-check the "to" address before proceeding.</Text>
</Box>
),
},
});
Image
Outputs an image. This component takes an inline SVG. It does not support remote URLs.
You can import SVG, PNG, and JPEG files using an import statement.
These files are automatically imported as SVG strings, so you can pass them directly to the
Image
component.
The SVG is rendered within an <img>
tag, which prevents JavaScript or interaction events from
being supported.
To disable image support, set the features.images
configuration option to false
.
The default is true
.
Props
src
:string
- An inline SVG.alt
:string
- An optional alternative text for the image.
Example
import { Box, Heading, Text, Image } from "@metamask/snaps-sdk/jsx";
import svgIcon from "./path/to/icon.svg";
module.exports.onHomePage = async () => {
return {
content: (
<Box>
<Heading>Hello world!</Heading>
<Text>Welcome to my Snap home page!</Text>
<Image src={svgIcon} />
</Box>
),
};
};
See the @metamask/images-example-snap
package for a full example of implementing images.
Input
Outputs an input component for use in interactive UI.