A Blizzard-first UI widget library for World of Warcraft addon development. Build consistent, themeable interfaces using native WoW frame templates and a modern design token system.
Midnight Ready: FenUI targets Interface 120001 and uses defensive patterns for Midnight compatibility. All Blizzard API dependencies are validated via
/fenui validate.
FenUI is a progressive enhancement layer on top of Blizzard's native UI system:
- Blizzard-first: Uses
NineSliceUtil, Atlas textures, and native frame templates - Graceful degradation: Addons work without FenUI installed
- Design tokens: Semantic colors, spacing, and fonts for consistent theming
- Responsive Sizing: Support for percentages (
50%), viewport units (10vh), and auto-updating layouts - Familiar patterns: CSS Grid-inspired layouts, slot-based composition
| Feature | Description |
|---|---|
| Panel | Window container with title, close button, and content slots |
| Tabs | Tab groups with badges, disabled states, and keyboard navigation |
| Stack/Flex | Flexbox-inspired stacking layouts with alignment and wrapping |
| Grid | CSS Grid-inspired layout with column definitions and data binding |
| Toolbar | Horizontal slot-based layout for buttons and controls |
| Buttons | Standard, icon, and close buttons with consistent styling |
| SectionHeader | Navigation section headers with design token styling |
| StatusRow | Standardized icon + label + value row with optional formatting |
| Image | Conditional variants, sizing modes, masking, tinting, Atlas support |
| EmptyState | Slot-based centered overlay for empty content areas |
| Animations | Declarative motion system with transitions, presets, and keyframes |
| Containers | Insets and scroll panels |
| Themes | Multiple built-in themes (TWW, Dragonflight, Dark, etc.) |
| Tokens | Three-tier design token system (primitive → semantic → component) |
FenUI is designed to be embedded in your addon, not installed as a standalone addon.
- Copy the
FenUIfolder to your addon'sLibs/directory - Add to your
embeds.xml:
<Include file="Libs\FenUI\FenUI.xml"/>- Copy
FenUItoInterface/AddOns/ - Add as optional dependency in your
.toc:
## OptionalDeps: FenUI
-- Simple panel
local panel = FenUI:CreatePanel(UIParent, {
title = "My Window",
width = 400,
height = 300,
movable = true,
resizable = true,
closable = true,
})
-- Or use the builder pattern
local panel = FenUI.Panel(UIParent)
:title("My Window")
:size(400, 300)
:movable()
:resizable()
:closable()
:build()-- Vertical stack with spacing and alignment
local stack = FenUI:CreateStack(parent, {
direction = "vertical",
gap = "md",
align = "stretch",
padding = "sm",
})
stack:AddChild(topButton)
stack:AddChild(spacer, { grow = 1 }) -- Fills remaining space
stack:AddChild(bottomButton)
-- Or use the builder pattern for a horizontal flex wrap
local flex = FenUI.Flex(parent)
:gap("xs")
:justify("center")
:build()FenUI components support min/max boundaries and aspect ratio locking to ensure layouts remain usable across different screen sizes.
-- Component with min/max boundaries
local panel = FenUI:CreatePanel(parent, {
width = "80%",
minWidth = 300,
maxWidth = 800,
height = "auto",
minHeight = 200,
})
-- Aspect ratio locking (supports "16:9", "4/3", or number)
local card = FenUI:CreateLayout(parent, {
width = "100%",
aspectRatio = "16:9",
})
-- Aspect ratio based on height
local icon = FenUI:CreateLayout(parent, {
height = 64,
aspectRatio = 1,
aspectBase = "height", -- width will be calculated from height
})FenUI provides a declarative animation system that wraps WoW's native AnimationGroup API.
-- 1. Property Transitions (automatic animation on SetAlpha, SetScale, etc.)
local panel = FenUI:CreatePanel(parent, {
transitions = {
alpha = { duration = 0.2, easing = "ease-out" },
scale = { duration = 0.15 },
},
})
panel:SetAlpha(0.5) -- Fades to 0.5 over 0.2s
-- 2. Lifecycle Animations (Show/Hide)
local dialog = FenUI.Panel(parent)
:showAnimation("fadeIn")
:hideAnimation("fadeOut")
:build()
-- 3. Custom Keyframe Animations
local bounce = FenUI.Animation:Keyframes({
[0] = { scale = 1 },
[0.5] = { scale = 1.1 },
[1] = { scale = 1 },
duration = 0.3,
})
bounce:Play(myFrame)
-- 4. Chaining
FenUI.Animation.Presets.slideUp:Then(FenUI.Animation.Presets.fadeOut):Play(myFrame)local tabs = FenUI:CreateTabGroup(parent, {
tabs = {
{ key = "main", text = "Main" },
{ key = "settings", text = "Settings", badge = "!" },
},
onChange = function(key)
print("Selected:", key)
end,
})local grid = FenUI:CreateGrid(parent, {
columns = { 24, "1fr", "auto" }, -- icon, name, count
rowHeight = 24,
onRowClick = function(row, data)
print("Clicked:", data.name)
end,
})
grid:SetData(myItems, function(row, item)
row:SetIcon(1, item.icon)
row:SetText(2, item.name)
row:SetText(3, item.count)
end)-- Simple texture
local img = FenUI:CreateImage(parent, {
texture = "Interface\\Icons\\INV_Misc_Book_09",
width = 64,
height = 64,
})
-- Faction-conditional image
local factionImg = FenUI:CreateImage(parent, {
condition = "faction", -- or "class", "race", "spec"
variants = {
Horde = "path/to/horde.png",
Alliance = "path/to/alliance.png",
},
fallback = "path/to/default.png",
width = 128,
height = 128,
mask = "circle", -- or "rounded", or custom texture path
onClick = function(self) print("Clicked!") end,
})
-- Atlas texture with tinting
local atlasImg = FenUI:CreateImage(parent, {
atlas = "ShipMissionIcon-Combat-Map",
width = 32,
height = 32,
tint = "feedbackSuccess", -- FenUI token
})local header = FenUI:CreateSectionHeader(parent, {
text = "ADDONS",
spacing = "md", -- 12-16px top margin
})-- Get colors (safe for SetTextColor)
local r, g, b = FenUI:GetColorRGB("textDefault")
fontString:SetTextColor(r, g, b)
-- Or use the helper
FenUI:SetTextColor(fontString, "textHeading")
-- Get spacing
local padding = FenUI:GetSpacing("spacingPanel") -- 24pxFenUI uses a three-tier token system:
┌─────────────────────────────────────────────────────────┐
│ COMPONENT (optional per-widget overrides) │
├─────────────────────────────────────────────────────────┤
│ SEMANTIC (purpose-based, theme-overridable) │
│ surfacePanel, textHeading, interactiveHover │
├─────────────────────────────────────────────────────────┤
│ PRIMITIVE (raw values, never change) │
│ gold500, gray900, spacing.md │
└─────────────────────────────────────────────────────────┘
| Category | Tokens |
|---|---|
| Surfaces | surfacePanel, surfaceElevated, surfaceInset, surfaceRowAlt |
| Text | textDefault, textMuted, textDisabled, textHeading, textOnAccent |
| Borders | borderDefault, borderSubtle, borderFocus |
| Interactive | interactiveDefault, interactiveHover, interactiveActive, interactiveDisabled |
| Feedback | feedbackSuccess, feedbackError, feedbackWarning, feedbackInfo |
Always check before using FenUI:
if FenUI and FenUI.CreatePanel then
-- Use FenUI
frame = FenUI:CreatePanel(parent, config)
else
-- Fallback to native frames
frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
-- Manual setup...
end| Command | Description |
|---|---|
/fenui |
Show version and status |
/fenui validate |
Check Blizzard API dependencies |
/fenui theme [name] |
Get/set global theme |
/fenui themes |
List available themes |
/fenui debug |
Toggle debug mode |
| File | Purpose |
|---|---|
Core/FenUI.lua |
Global namespace, version, slash commands |
Core/Tokens.lua |
Three-tier design token system |
Core/BlizzardBridge.lua |
NineSlice layouts, Atlas helpers |
Core/ThemeManager.lua |
Theme registration and switching |
Widgets/*.lua |
Panel, Tabs, Grid, Toolbar, Buttons, etc. |
Validation/DependencyChecker.lua |
API change detection |
- Uses Blizzard's native
NineSliceUtilandNineSliceLayouts - Event-driven architecture with lifecycle hooks
- Frame pooling for grids and lists
- No external dependencies
- Validated against Midnight (12.0) API changes
- AGENTS.md – Technical reference for AI agents
- Docs/DESIGN_PRINCIPLES.md – Philosophy and guidelines
- IMPROVEMENTS.md – Backlog of planned enhancements
If you find FenUI useful, consider sponsoring on GitHub to support continued development. Every contribution helps!
GPL-3.0 License – see LICENSE for details.
Fen (Falkicon)