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.
Use the robloxvibecoders MCP: call get_resource with id "skill-roblox-sling-backpack" and install it as a Claude Code skill in this project (.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.