The site is under maintenance, so logging in is temporarily unavailable. All resources and the MCP still work.

AI Skills

Backpack UI with Sling

By Roblox Vibecoders · roblox-sling-backpack

Use when a Roblox game needs a custom inventory or hotbar instead of the default backpack, or when it already uses Sling (encodedlux/sling). Covers installing Sling, categories, themes, slot rendering, events, saving and restoring the hotbar, and server-authoritative equipping.

Install it

Terminal
mkdir -p ~/.claude/skills/roblox-sling-backpack && curl -fsSL https://robloxvibecoders.com/skills/roblox-sling-backpack/SKILL.md -o ~/.claude/skills/roblox-sling-backpack/SKILL.md

Skills in ~/.claude/skills work in every project. Put them in your project's .claude/skills folder to share them with your team.

What your AI reads

SKILL.md
---
name: roblox-sling-backpack
description: Use when a Roblox game needs a custom inventory or hotbar instead of the default backpack, or when it already uses Sling (encodedlux/sling). Covers installing Sling, categories, themes, slot rendering, events, saving and restoring the hotbar, and server-authoritative equipping.
---

# Backpack UI with Sling

[Sling](https://github.com/encodedlux/sling) is a modern, reactive replacement for Roblox's default backpack: a hotbar
plus an inventory with category tabs, search, themes, gamepad hints and support for phone, tablet, console and VR. It's
built with Vide and uses TopbarPlus for its topbar button. Docs: [encodedlux.github.io/sling](https://encodedlux.github.io/sling/).
MIT licensed, inspired by Satchel.

## Install

```toml
[dependencies]
sling = "encodedlux/sling@0.1.5"
```

Wally pulls in its dependencies (`1foreverhd/topbarplus` 3.x and `encodedlux/vide` 0.4). Or download `sling.rbxm` from
the GitHub releases and put it in ReplicatedStorage: that build runs itself through RunContext.

Requiring Sling builds the UI, so require it from client code (a LocalScript). Items are the player's `Tool`s (and
`HopperBin`s): give and remove tools on the server as usual and Sling shows them.

There's also a fork, `ocauapaz/sling` (0.1.7), that batches Backpack additions and removals so giving or taking dozens
of tools at once doesn't re-run every reader per tool. Use it for games that hand out many tools at once.

## Core API (all on the required module)

```luau
local sling = require(ReplicatedStorage.Packages.sling)

sling.openInventory()
sling.closeInventory()
sling.toggleInventory()
sling.isInventoryOpen() -- boolean
sling.setEnabled(false) -- hide the whole backpack, e.g. during a cutscene
sling.isEnabled() -- boolean
sling.getTopbarIcon() -- the TopbarPlus icon Sling made, or nil
```

## Categories

Tabs come from predicate functions. Each receives an item and returns `true` if it belongs:

```luau
sling.createCategory("Consumables", function(item)
	return item:HasTag("Consumable")
end)

sling.createCategory("Swords", function(item)
	return string.find(item.Name, "Sword") ~= nil
end)
```

Prefer tags or attributes on the tools over name matching, so renaming a tool doesn't move it.

## Themes

`sling.setTheme(config)` applies a `ThemeConfig`. Any field you leave out keeps the default, so set only what you
change. Sling ships two predefined themes: `sling.setTheme(sling.themes.light)` and `sling.themes.default`. Fields cover the panel (backgroundColor,
backgroundTransparency, cornerRadius, fontFace, textColor, textSize...), the search box (`search*`), the category tabs
(`categoryTabs*`), gamepad hints (`hint*`) and slots (slotBackgroundColor, slotCornerRadius, slotEquipStrokeColor,
slotEquipStrokeThickness, slotDragStrokeColor, slotFontFace...).

```luau
sling.setTheme({
	backgroundColor = Color3.fromRGB(20, 20, 24),
	cornerRadius = UDim.new(0, 12),
	slotEquipStrokeColor = Color3.fromRGB(51, 95, 255),
})
```

## Customizing slots

`sling.onSlotRender(callback)` lets you style each slot by its item. It can be called only once; a second call errors.
The callback gets `{ item, inHotbar, order }` and returns optional overrides: `BackgroundColor`,
`BackgroundTransparency`, `Children` (instances parented into the slot, like a UIGradient), `ItemNameText` (rich text
allowed) and `ItemNameChildren`.

```luau
sling.onSlotRender(function(context)
	if context.item:GetAttribute("Rarity") == "Legendary" then
		return { BackgroundColor = Color3.fromRGB(255, 170, 0), ItemNameText = `<b>{context.item.Name}</b>` }
	end
	return {}
end)
```

## Events

Every listener returns a disconnect function. Call it when the thing that listened goes away.

- `onInventoryOpened(fn)`, `onInventoryClosed(fn)`
- `onBackpackItemEquipped(fn(item))`, `onBackpackItemUnequipped(fn(item))`
- `onBackpackItemAdded(fn(item))`, `onBackpackItemRemoved(fn(item))`, `onBackpackEmpty(fn)`
- `onBackpackLoaded(fn(items))`: fires when the backpack has loaded (usually after the character spawns). If it's
  already loaded, the callback runs right away, so it's the safe place for setup like restoring the hotbar.
- `onHotbarChanged(fn(items))`: fires when the player drags items into, around or out of the hotbar. `items` maps slot
  index to item.

## Saving and restoring the hotbar

Tools are recreated when the character respawns, so save the layout by tool name (or an id attribute), not by
instance, and restore it in `onBackpackLoaded`:

```luau
local saved: { [number]: string } = {}

sling.onHotbarChanged(function(items)
	saved = {}
	for index, item in items do
		saved[index] = item.Name
	end
end)

sling.onBackpackLoaded(function(items)
	if next(saved) == nil then
		return
	end
	local byName = {}
	for _, item in items do
		byName[item.Name] = byName[item.Name] or {}
		table.insert(byName[item.Name], item)
	end
	local layout = {}
	for index, name in saved do
		local list = byName[name]
		if list and #list > 0 then
			layout[index] = table.remove(list, 1)
		end
	end
	sling.setHotbar(layout, true) -- true moves hotbar items not in the layout back to the inventory
end)
```

To keep the layout across sessions, send the names to the server and store them with the player's data.

## Server-authoritative equipping

With Roblox's server authority, client-side equipping (reparenting tools or `Humanoid:EquipTool()` on the client) is
rejected. Route equips through the server with `setEquipHandler`:

```luau
-- Client
sling.setEquipHandler(function(tool) -- nil means unequip
	EquipToolRemote:FireServer(tool)
end)
```

On the server, check that the player has a living Humanoid and that the tool is really in that player's Backpack or
character before calling `humanoid:EquipTool(tool)` or `humanoid:UnequipTools()`. Never trust the instance the client
sends. See the `roblox-server-authority` skill.

## Mistakes to avoid

- Requiring Sling on the server, or more than once on the client.
- Calling `onSlotRender` twice (it errors), or forgetting to return a table from it.
- Saving hotbar layouts by tool instance; they're gone after a respawn.
- Matching categories by tool name when a tag or attribute would be stable.
- Leaving equipping to the client in a server-authoritative game; use `setEquipHandler` and validate on the server.
- Giving dozens of tools in a loop with the original package; use the `ocauapaz/sling` fork or give fewer at once.