Start typing to search packages!
itemdisplayrenderer
By @biotoxin495
Roblox
Mirrored from WallyItemDisplayRenderer — A reusable UI rendering registry for Roblox
A dependency-free, centralized item and reward visualization system for Roblox UI.
ItemDisplayRenderer lets a game register the visual behavior for a content type once, then reuse that renderer across reward screens, inventories, shops, quests, collectibles, purchase results, and other interfaces.
The module combines two common rendering workflows under one lifecycle:
Create()— the renderer creates/owns its display frame.RenderInto()— the caller already owns the target frame and the renderer decorates it.
Both return the same render handle, support the same options, and clean up through the same lifecycle.
Quick example
Register a renderer once, then create its UI anywhere that needs to display that content type.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ItemDisplayRenderer = require(ReplicatedStorage.Packages.ItemDisplayRenderer)
local Renderer = ItemDisplayRenderer.new()
Renderer:Register("Coins", {
Create = function()
return CoinTemplate:Clone()
end,
Render = function(context)
context.Frame.Amount.Text = tostring(context.Data.Amount)
end,
})
local handle = Renderer:Create("Coins", RewardsContainer, {
Amount = 500,
})
🚀 Features
One renderer, two workflows
Use Create() when the renderer should create and own its display frame. Use RenderInto() when another system already owns the target frame. Both workflows return the same handle and use the same update and cleanup behavior.
Central renderer registry
Register visual behavior once and reuse it across reward screens, inventories, shops, quests, collectibles, and purchase results.
Deterministic lifecycle
Every render has a handle that can be inspected, updated, or destroyed. Cleanup runs when a render is updated, replaced, destroyed, or when its target GuiObject is destroyed.
Lists, variants, and templates
Render ordered lists with duplicate renderer IDs, select presentation variants through display options, and create frames with functions or reusable templates.
Optional integrations
Popup adapters, asynchronous work, and animated ViewportFrame renderers remain opt-in and dependency-free.
Fully typed, no dependencies
ItemDisplayRenderer uses strict Luau and does not require Kernel, Maid, Promise, Signal, or another framework.
📖 Basic usage
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ItemDisplayRenderer = require(
ReplicatedStorage.Packages.ItemDisplayRenderer
)
local Renderer = ItemDisplayRenderer.new()
Register a renderer
Every renderer must define Render(context).
If it should also be usable through Create(), give it either a Create(context) function, a Template, or a Templates table.
Renderer:Register("Coins", {
Create = function(context)
local frame = CoinTemplate:Clone()
return frame
end,
Render = function(context)
context.Frame.Amount.Text = tostring(context.Data.Amount)
return function()
-- Optional cleanup.
end
end,
})
The cleanup function returned by Render() runs when the render is updated, replaced, destroyed, or when its target frame is destroyed.
Renderer:Create(rendererId, parent, data, options?)
Use Create() when the renderer should create its own frame.
local handle = Renderer:Create("Coins", RewardsContainer, {
Amount = 5000,
})
The resulting frame can be obtained from the handle:
local frame = handle:GetFrame()
By default, a frame created through Create() is destroyed when its handle is destroyed.
Renderer:RenderInto(rendererId, frame, data, options?)
Use RenderInto() when the surrounding system already owns the frame.
local handle = Renderer:RenderInto("Coins", ExistingFrame, {
Amount = 5000,
})
Destroying this handle runs renderer cleanup but keeps ExistingFrame, because the renderer does not own it.
This is useful for Studio-authored inventory slots, shop cards, reward entries, or any UI where the surrounding feature owns the layout.
Render handle API
Every successful render returns a handle.
local handle = Renderer:Create(...)
Check its state
if handle:IsActive() then
print("Still active")
end
Read its frame
local frame = handle:GetFrame()
Update it
handle:Update({
Amount = 10000,
})
Update() retains the target GuiObject, cleans up the previous rendering lifecycle, then runs the renderer again with the new data.
This means every renderer automatically supports updates without needing a separate update callback.
Destroy it
handle:Destroy()
This runs:
- popup cleanup,
- renderer cleanup,
- automatic lifecycle disconnection,
- frame destruction if the renderer owns the frame.
Render replacement
Only one ItemDisplayRenderer render owns a target frame at a time.
Calling RenderInto() on a frame that already has an active renderer automatically cleans up the old render before attaching the new one.
Renderer:RenderInto("Coins", Slot, coinData)
Renderer:RenderInto("Pet", Slot, petData)
The second call safely replaces the first render while preserving Slot itself.
Set:
{
ReplaceExisting = false,
}
if replacement should be rejected instead.
Ordered render lists
Lists use ordered request descriptors rather than renderer IDs as table keys.
local handles = Renderer:RenderList(RewardsContainer, {
{
Renderer = "Coins",
Data = { Amount = 500 },
},
{
Renderer = "Coins",
Data = { Amount = 2500 },
},
{
Renderer = "Pet",
Data = { Name = "Golden Cat" },
},
})
Because this is an array, duplicate renderer types are supported and ordering is deterministic.
Display variants
Presentation-specific state belongs in display options rather than domain data.
Renderer:Create("Pet", Container, {
Name = "Golden Cat",
}, {
Variant = "Expanded",
})
Inside the renderer:
Render = function(context)
if context.Variant == "Expanded" then
-- Expanded presentation.
else
-- Default presentation.
end
end
This replaces patterns such as _showExpanded mixed into reward/item data.
Templates
Instead of implementing Create(), a renderer can use a template:
Renderer:Register("Coins", {
Template = CoinTemplate,
Render = function(context)
context.Frame.Amount.Text = tostring(context.Data.Amount)
end,
})
Or variant-specific templates:
Renderer:Register("Pet", {
Templates = {
Default = CompactPetTemplate,
Expanded = ExpandedPetTemplate,
},
Render = function(context)
-- Populate whichever template was selected.
end,
})
The module does not require UI to be built through code. Templates may be authored in Studio or created at runtime.
Popup and tooltip adapters
Popups are optional and deliberately kept outside the core module.
Construct the renderer with an adapter:
local Renderer = ItemDisplayRenderer.new({
PopupAdapter = MyPopupAdapter,
})
A renderer can then return a popup descriptor:
Popup = function(context)
return {
Type = "CurrencyPopup",
Data = {
CurrencyType = "Coins",
Amount = context.Data.Amount,
},
}
end
Enable it per render:
Renderer:Create("Coins", Container, data, {
Popup = true,
})
The adapter contract is:
function MyPopupAdapter:Register(frame, descriptor, context)
-- Attach hover/click behavior.
return function()
-- Remove it again.
end
end
Async-safe renderers
Some renderers may need asynchronous work, such as avatar thumbnails or remote/config lookups.
Use:
context:IsActive()
before applying delayed results.
task.spawn(function()
local result = getSomethingAsync()
if context:IsActive() then
context.Frame.Icon.Image = result
end
end)
This prevents an old asynchronous operation from mutating a frame after that render has been replaced or destroyed.
ViewportFrame and animated renderers
A renderer can create connections or run a 3D preview and return the matching cleanup function.
Render = function(context)
local connection = RunService.RenderStepped:Connect(function(dt)
-- Animate the preview.
end)
return function()
connection:Disconnect()
-- Clear the ViewportFrame.
end
end
Updating or destroying the render disconnects its RenderStepped connection through the returned cleanup function.
Renderer packs with RegisterMany
Renderer packs can be grouped into separate modules and registered together.
Renderer:RegisterMany({
Coins = CoinRenderer,
Gems = GemRenderer,
Pet = PetRenderer,
})
This lets a larger project organize definitions into packages such as:
Renderers/
├── CurrencyRenderers.lua
├── PetRenderers.lua
├── CollectibleRenderers.lua
└── PowerupRenderers.lua
⚙️ API
ItemDisplayRenderer.new(config?)
Creates an independent renderer registry. Configuration may provide a popup adapter, enable strict errors, or change the default variant name.
Registry methods
| Method | Description |
|---|---|
Register(rendererId, renderer, options?) | Registers one renderer and returns whether registration succeeded |
RegisterMany(renderers, options?) | Registers a dictionary of renderer definitions |
Unregister(rendererId) | Removes a renderer registration |
GetRenderer(rendererId) | Returns the registered definition, if present |
HasRenderer(rendererId) | Reports whether an ID is registered |
Pass { Override = true } to Register or RegisterMany to deliberately replace an existing registration.
Rendering methods
| Method | Description |
|---|---|
Create(rendererId, parent, data, options?) | Creates a frame, renders it, and returns its handle |
RenderInto(rendererId, frame, data, options?) | Renders into a caller-owned frame and returns its handle |
RenderList(parent, requests, options?) | Creates an ordered list and returns its successful handles |
EndRendering(frame) | Destroys the active handle associated with a frame |
GetActiveHandle(frame) | Returns the active handle associated with a frame |
Manager lifecycle methods
| Method | Description |
|---|---|
SetPopupAdapter(adapter) | Replaces or clears the popup adapter |
Destroy() | Destroys active handles, clears registrations, and releases the adapter |
Handle methods
| Method | Description |
|---|---|
IsActive() | Reports whether the handle is still active |
GetFrame() | Returns the rendered GuiObject |
GetRendererId() | Returns the registered renderer ID |
GetData() | Returns the handle's current data |
GetOptions() | Returns the resolved display options |
Update(data, options?) | Cleans up the current render and renders new state into the same frame |
Destroy() | Runs cleanup and destroys the frame when the handle owns it |
Complete options reference
You normally only need to provide the values that differ from the defaults.
Constructor configuration
| Property | Type | Description |
|---|---|---|
PopupAdapter | PopupAdapter? | Adapter used to attach popup or tooltip behavior |
Strict | boolean? | Raises errors instead of warnings for runtime rendering failures |
DefaultVariant | string? | Fallback variant name; defaults to "Default" |
Display options
| Property | Type | Description |
|---|---|---|
Variant | string? | Presentation variant exposed as context.Variant |
Popup | boolean? | Enables the renderer's popup descriptor for this render |
ReplaceExisting | boolean? | Allows replacement of an active render on the same frame; defaults to true |
DestroyFrameOnCleanup | boolean? | Controls whether a frame created by Create() is destroyed with its handle |
Metadata | { [any]: any }? | Caller-defined presentation metadata available through context.Options |
Renderer definition
| Member | Type | Description |
|---|---|---|
Render | (context) -> cleanupFunction? | Required function that applies data to the target frame |
Create | (context) -> GuiObject? | Optional factory used by Create() |
Template | GuiObject? | Optional default template cloned by Create() |
Templates | { [string]: GuiObject }? | Optional templates indexed by variant name |
Popup | (context) -> PopupDescriptor? | Optional popup descriptor factory |
Display context
| Member | Description |
|---|---|
RendererId | ID of the active renderer |
Frame | Target GuiObject; nil only while a custom Create function is running |
Parent | Requested or current parent instance |
Data | Domain data supplied by the caller |
Options | Resolved display options |
Variant | Selected variant or configured default |
Manager | ItemDisplayRenderer instance managing the render |
Handle | Active render handle; nil while creating the frame |
IsActive() | Reports whether delayed work still belongs to an active render |
Architecture boundary
ItemDisplayRenderer is a presentation package.
It should not grant rewards or authoritatively mutate player data.
A typical flow is:
Server reward / inventory logic
│
▼
item/reward descriptor
│
▼
Client
│
▼
ItemDisplayRenderer
│
▼
Roblox UI
Your reward system determines that a player receives 500 Coins. ItemDisplayRenderer determines how those 500 Coins look in a given interface.
Migrating from the old two-manager design
The old MainDisplayManager use case maps to RenderInto():
local handle = Renderer:RenderInto("Pet", existingFrame, data, {
Popup = true,
})
The old RewardVisualizerOnFrameManager use case maps to Create():
local handle = Renderer:Create("Pet", rewardsContainer, data, {
Popup = true,
})
Both now share the same registry, options, handle type, popup integration, update behavior, and cleanup lifecycle.
Behavior
Register how something should appear once, then reuse that visual behavior anywhere.
The consuming feature owns its game logic and surrounding UI. ItemDisplayRenderer owns the visualization lifecycle.
📝 Notes
- A renderer must provide
Render(context). Create()additionally requiresCreate(context),Template, orTemplates.- Only one active ItemDisplayRenderer render may own a target frame at a time.
- Use
context:IsActive()before applying delayed asynchronous results. - ItemDisplayRenderer is a presentation package; authoritative inventory and reward logic belongs elsewhere.
- Call
Renderer:Destroy()when the rendering context is no longer needed.
🛠️ Installation
Create a ModuleScript named ItemDisplayRenderer, copy the contents of init.luau into it, and place it at:
ReplicatedStorage
└── Packages
└── ItemDisplayRenderer
Require the module from a client script wherever your packages are mapped.
License
This project is released under the MIT License.
See LICENSE for details.
made with ❤️ by biotoxin495
Package Details
Install command (Click to copy)
Version
1.0.0
License
MIT
Safe for commercial use
Automated license review — not legal advice.
