> For the complete documentation index, see [llms.txt](https://seanime.gitbook.io/seanime-extensions/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/tray.md).

# Tray

<figure><img src="/files/C3LBPm1VXD8ebL6llLvy" alt="" width="563"><figcaption><p>Example</p></figcaption></figure>

## Create a tray icon

{% hint style="info" %}
You can only create one tray icon in your plugin
{% endhint %}

```typescript
const tray = ctx.newTray({
    tooltipText: "My plugin",
    iconUrl: "https://seanime.rahim.app/logo_2.png",
    withContent: true,
    isDrawer: false, // Choose whether the tray contents are displayed in a drawer
})
```

## Add a badge

```typescript
tray.updateBadge({ number: 1, intent: "alert" })
// Remove the badge
tray.updateBadge({ number: 0 })
```

* `intent`: "alert" | "info" | "warning" | "success"

## Rendering content

{% hint style="info" %}
`withContent` should be set to 'true'.
{% endhint %}

The `state` is a mechanism to keep track of variable data over time, enabling components to re-render automatically when the data changes.

The `render()` function is used to define how content should be presented in the tray popover. It takes a callback function that returns a tree of components.

Components, like `tray.text` and `tray.button`, are reusable building blocks of the UI, each responsible for rendering a piece of the interface according to the current state.

The tray will be re-rendered anytime there is a state change even if the state isn't in the render function.

<pre class="language-typescript"><code class="lang-typescript">const count = ctx.state(0);

ctx.setInterval(() => {
<strong>    count.set(c => c + 1);
</strong>}, 1000);

tray.render(() => {
    return tray.stack({
        items: [
<strong>            tray.text(`Count: ${count.get()}`),
</strong>            // Show the button when the count reaches 5
<strong>            count.get() >= 5
</strong>                ? tray.button("Click me", { onClick: "button-clicked" })
                : trat.text("Nothing here")
        ],
    })
})
</code></pre>

## Tray events

```typescript
tray.onOpen(() => {
    // User opened the tray content
})

tray.onClose(() => {
    // User closed the tray content
})

tray.onClick(() => {
    // User clicked the tray icon
})

// Open the tray
// This will not work on the first page load or if the plugin is not pinned
tray.open()

// Close the tray
tray.close()

// Force the tray content to update
// Not useful is most cases because the tray updates when states change
tray.update()
```

## Event handlers

You can register functions to specific event triggers like `onClick` using `ctx.registerEventHandler()` in order to define custom behaviors based on user action.

<pre class="language-typescript"><code class="lang-typescript">//..

<strong>ctx.registerEventHandler("reset-counter", () => {
</strong><strong>    count.set(0)
</strong><strong>})
</strong>
tray.render(() => {
    return tray.stack({
        items: [
            tray.text(`Count: ${count.get()}`),
<strong>            tray.button("Reset counter", { onClick: "reset-counter" }),
</strong>        ],
    })
})
</code></pre>

#### Tips

You can register inline event handlers. Make sure the first argument is unique to that element.

```typescript
tray.stack(allItems.map((item) => {
    return tray.flex([
        tray.text(key),
        tray.button({ 
            label: "Open", 
            size: "sm", 
            // It takes a unique ID key as first argument!
            onClick: ctx.eventHandler(item.id, () => {
                // Do something...
            }),
            intent: "gray-subtle"
         }),
    ], { gap: 1, style: { alignItems: "center" } })
}))
```

## Base components

```typescript
tray.div([], { style: {} })
tray.stack([], { style: {} })
tray.flex([], { style: {} })
tray.text(...)
tray.anchor(...)
tray.a(...)
tray.p(...)
tray.span(...)
tray.css(...)
tray.badge(...)
tray.alert(...)
tray.img(...)
```

## Fields/Forms

Field components:

* input
* button
* select
* radioGroup
* checkbox
* switch

Use `ctx.fieldRef` to get and set a field's value synchronously.

<pre class="language-typescript"><code class="lang-typescript"><strong>const textInputRef = ctx.fieldRef&#x3C;string>("Default value")
</strong>
// When the form is submitted
ctx.registerEventHandler("submit-form", () => {
    // We can get the value of the text input
<strong>    console.log(textInputRef.current)
</strong>    
    // We can change the value of the text input
<strong>    textInputRef.setValue("")
</strong>})

tray.render(() => tray.stack([
<strong>    text.input("A text field", { fieldRef: textInputRef }),
</strong>    tray.button("Submit", { onClick: "submit-form" }),
]))
</code></pre>

### Select, RadioGroup

You can create forms easily with `ctx.fieldRef` and the available field components.

```typescript
const selectRef = ctx.fieldRef<string>()
const radioGroupRef = ctx.fieldRef<string>()

tray.render(() => tray.stack([
    tray.select("Label", { 
        placeholder: "Select...",
        options: [
            { label: "One Piece", value: "21" },
            { label: "Sakamoto Days", value: "177709" },
        ],
        fieldRef: selectRef,
    }),
    tray.radioGroup("Label", { 
        options: [
            { label: "One Piece", value: "21" },
            { label: "Sakamoto Days", value: "177709" },
        ],
        fieldRef: radioGroupRef,
    }),
]))
```

### Checkbox, Switch

```typescript
const checkboxRef = ctx.fieldRef<boolean>()
const switchRef = ctx.fieldRef<boolean>()

tray.render(() => tray.stack([
    tray.checkbox("Do something", { 
        fieldRef: checkboxRef
    }),
    tray.switch("Do something else", { 
        fieldRef: switchRef
    }),
]))
```

## Complex components

### CSS

```javascript
tray.div([
    tray.stack([
        tray.css(`
        .red { background-color: red; }
        `),
        // Red square
        tray.tooltip(tray.div([], { className: "square red relative" }), { text: "Test tooltip" }),
    ]),
    tray.stack([
        // No red
        tray.tooltip(tray.div([], { className: "square red relative" }), { text: "Test tooltip" }),
    ]),
])
```

### Tabs, Dropdown, Modal

```javascript
tray.tabs([
    tray.tabsList([
        tray.tabsTrigger(tray.span("Item 1"), { value: "1" }),
        tray.tabsTrigger(tray.span("Item 2"), { value: "2" }),
    ]),
    tray.tabsContent([
        tray.text("Hello, World!"),
        tray.a([
            tray.span("A "),
            tray.span("link", { className: "font-bold" }),
        ], { href: "#" }),
        tray.p([
            tray.span("This is a paragraph."),
        ]),
        tray.modal({
            trigger: tray.button("Open modal"),
            open: modalOpen.get(),
            onOpenChange: ctx.eventHandler("modal-open-change", ({ open }) => {
                console.log(open)
                modalOpen.set(open)
            }),
            items: [
                tray.text("Hello, World!"),
            ],
        }),
        tray.dropdownMenu({
            trigger: tray.button("Open dropdown"),
            items: [
                tray.dropdownMenuItem(tray.span("Item 1")),
                tray.dropdownMenuItem(tray.span("Item 2")),
                tray.dropdownMenuItem(tray.span("Item 3")),
            ],
        }),
    ], { value: "1" }),
    tray.tabsContent([
        tray.text("Item 2 content"),
    ], { value: "2" }),
], { defaultValue: "1" }),
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/tray.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
