Luix · v1.5.4 — Smart UI Authoring for VS Code | React-Luau · Roact · Fusion · Vide | Prop hints, RichText, asset previews & more

:bullseye: One VS Code extension that gives React-Luau, Roact, Fusion, and Vide users the same prop hints, hover docs, color pickers, RichText tooling, and asset previews.

Install on VS Code Marketplace View source on GitHub MIT License

I have in recent months switched over to VS Code to design my Roblox UI using Roblox frameworks. Though personally, I’ve noticed that there are a lot of redundant or repetitive tasks which could be automated easily such as prop hints, RichText highlighting, asset previews, autocomplete, and much more. This is why I built Luix, a single extension which creates a consistant UI-authoring intelligence for React-Luau, Roact, Fusion, and Vide out of the box.

Whichever framework you’re writing in, the same hover docs, the same prop completions, the same color picker, the same RichText helpers, the same diagnostics. One extension, every framework.


:sparkles: Why Luix

  • Framework-agnostic. Type e("Frame", { … }), New "Frame" { … }, create "Frame" { … }, or Luau backtick template strings; all of them get the same intelligence.
  • Smart, not noisy. Diagnostics catch real bugs (ScrollingDirection on a Frame, TextColor typo, unused declared props, hard-coded prop overrides) and offer one-click fixes.
  • Visual when it matters. Image previews on hover and in the gutter; full webview editors for gradients and sprite atlases; inline color pickers for every Color3 form; inline SVG previews for TweenInfo, UIPadding, UICorner, UIStroke.
  • Refactor-aware. Extract any element tree into a new component file with only the imports it actually uses. Convert between Color3 / UDim2 forms. Wrap selections in containers. Sort props by category on demand or on save.

:bullseye: Features

Prop completion + type-aware snippets

Click to expand

When you type any prop name inside an element table a snippet wired up with tab stops will show up similar to the example below:

e("TextLabel", {
    Back|       --> BackgroundColor3 = Color3.fromRGB(255, 255, 255),
    Size = U|   --> Size = UDim2.new(0, 0, 0, 0),
    Font|       --> FontFace = Font.fromName("Montserrat", Enum.FontWeight.Regular),
    Hor|        --> HorizontalAlignment = Enum.HorizontalAlignment.,
})

Works identically across all four frameworks. Color3 placeholder format toggleable between fromRGB / fromHex / new / fromHSV via luix.color3.defaultFormat.

Class-name picker the moment you type e("

Click to expand

Picture3

e("Fr|")        --> accept "Frame" → e("Frame", { <cursor> })
New "Sc|"       --> accept "ScrollingFrame" → New "ScrollingFrame" { <cursor> }
create `Im|`    --> accept "ImageLabel" → create `ImageLabel` { <cursor> }

When the call has no props table yet, accepting also adds , { … } (parens form) or { … } (curried) with the cursor parked inside.

anchor: preset shortcut

Click to expand

Picture4

I personally position my AnchorPoint’s and Position properties in the sample places and this tool is here to help combat the constant rewriting of the most common 9 positions you like to place the element. Inside any props table just do:

e("Frame", {
    anchor:br|   --> AnchorPoint = Vector2.new(1, 1),
                 --> Position = UDim2.fromScale(1, 1),
})

9 presets: tl t tr l c r bl b br. Paired with an auto-detect diagnostic that flags Position = UDim2.fromScale(0.5, 0.5) without a matching anchor — one-click fix to add it.

RichText completion

Click to expand

Picture5

Typing < inside Text = "…" opens a tag picker — <b>, <i>, <font>, <stroke>, <mark>, <br/>, and more. Attribute completion chains multiple attributes inside <font …>:

Text = "<font color=\"#FF0000\" size=\"24\" weight=\"Bold\">Hello</font>",
        ^-- typing `<` opens the tag picker
  • Auto-closes </font> when you finish the opening >
  • Inline color picker on color="…" values
  • Warns when Text contains tags but RichText = true isn’t set (one-click fix)
  • Inner attribute quotes adapt to the outer Lua quote so "…", '…', and Luau backticks all work without backslash escaping

Roblox custom glyphs

Click to expand

Roblox’s icon set (Robux, Premium, Verified, Roblox Plus) lives in the Unicode private-use area, which VS Code renders as [] boxes. Luix annotates them:

Text = "Cost: ⏹ 1,199"   -- inlay hint reads: ⏹ Robux

Type :robux:, :premium:, :verified:, or :roblox-plus: to insert the literal glyph. Add your own via luix.robloxGlyphs.custom:

{ "luix.robloxGlyphs.custom": { "gbp": "£", "shrug": "¯\\_(ツ)_/¯" } }

Image-asset preview

Click to expand

Hover any "rbxassetid://NNNN" → see the actual Roblox CDN thumbnail in the tooltip (resolved via thumbnails.roblox.com, cached per session).

Opt in to gutter previews for inline thumbnails:

Image = "rbxassetid://13868180889",  -- thumbnail appears in the gutter

Cache lives in VS Code’s extension storage (or .luix/assetThumbs/ per workspace, with an auto-written .gitignore). Sidebar shows live cache size + a one-click purge.

Gradient editor (new in v1.4.0)

Click to expand

An Edit UIGradient CodeLens above every e("UIGradient", { … }) opens a combined webview: drag triangle stops on the color ramp, drag circles on the transparency curve, slide rotation −180°..180°, and watch the preview square update live. The preview multiplies by the parent element’s BackgroundColor3 so what you see is what Roblox renders.

Standalone ColorSequence.new(…) and NumberSequence.new(…) literals get a focused per-literal editor with the same UX. Default values (white color, zero transparency, zero rotation) are stripped on write-back, so no noise lands in your code.

Output respects luix.color3.defaultFormat (fromRGB / fromHex / new). Hover previews for both sequence types are on by default.

Sprite-rect editor (new in v1.4.0)

Click to expand

An Edit sprite rect CodeLens appears above every e("ImageLabel" | "ImageButton", { … }) whose Image prop is a literal rbxassetid://…. The webview pulls the asset thumbnail, lets you drag the rect with 8 resize handles, and writes ImageRectOffset / ImageRectSize back to your code.

  • Aspect auto-detect — reads a sibling UIAspectRatioConstraint or Size = UDim2.fromOffset(…) to pre-fill the frame aspect, and renders a dashed yellow ScaleType.Crop preview inside your selection.
  • Native dimensions via Open Cloud (opt-in) — set luix.openCloud.apiKey to a Roblox API key with the legacy-asset:manage permission; the editor auto-fetches each asset’s true pixel size once and caches it forever. Without a key you type Source W / Source H manually — also cached per asset.
  • Scroll-to-zoom canvas (15–300%) with a discoverable zoom bar; rect can exceed source dims (4x cap, overflow highlighted with a dashed amber border).
  • “Full image” defaults ((0,0) offset, (0,0) size) are stripped on apply.

Visual hover previews (new in v1.4.0)

Click to expand

Inline SVG, rendered straight from your literal values — no network, no caching:

  • TweenInfo.new(...) — 240×140 graph of the easing curve, all 12 EasingStyle × 4 EasingDirection combinations supported, with duration / repeat / reverses / delay summary.
  • e("UIPadding", { … }) — box visualisation with the inner content area indented by the configured PaddingTop / Right / Bottom / Left. Each non-zero side gets its pixel value labeled.
  • e("UICorner", { … }) — rounded rectangle at the configured CornerRadius.
  • e("UIStroke", { … }) — sample box with the stroke applied at the configured Thickness / Color / Transparency.

Toggle with luix.hoverPreviews.enabled (on by default).

Sort props by category (new in v1.4.0)

Click to expand

Right-click anywhere inside a props table and you’ll be able to press the Sort props by category button which reorders props into a specified order: Identification → Layout → Style → Visibility → Image → Text → Behavior → Events → Refs → Children → Other.

e("TextLabel", {                         e("TextLabel", {
    TextColor3 = …,                          Name = "Greeting",
    Size = UDim2.fromOffset(…),              Size = UDim2.fromOffset(…),
    Name = "Greeting",             ->        BackgroundColor3 = …,
    BackgroundColor3 = …,                    TextColor3 = …,
    [React.Event.Activated] = …,             Text = "Hello",
    Text = "Hello",                          [React.Event.Activated] = …,
})                                       })
  • Format-on-save — luix.sortProps.onSave (default false). When enabled, every props table in the file sorts on save. Off by default so saving a file doesn’t reshape your layout without you wanting it to.
  • Configurable order — luix.sortProps.categoryOrder (string array) lets you reorder or remove categories.
  • Computed-key + comment safe — captures [React.Event.Activated], [OnEvent "…"], [Children], etc. Tables with -- comments are skipped so comments never get detached.

Diagnostics

Click to expand

e("Frame", {
    ScrollingDirection = Enum.ScrollingDirection.Y,
 -- ~~~~~~~~~~~~~~~~~~ Unknown property `ScrollingDirection` on `Frame`.
 --                     💡 Did you mean `Position`?
    Size = UDim2.fromScale(1, 1),
    Size = UDim2.new(0, 200, 0, 50),
 -- ~~~~ Duplicate prop `Size` — the second assignment overwrites the first.
    BorderMode = Enum.Font.SourceSans,
 --              ~~~~~~~~~~~~~~~~~~~~~ Expects `Enum.BorderMode`, got `Enum.Font`.
    Transparency = 1.5,
 --                ~~~ Typically in `0..1` — got `1.5`.
})

Plus: missing AnchorPoint, missing RichText = true, TextScaled collapse-to-zero gotcha, deprecated Font = Enum.Font.X, typo’d TextColor, and an optional WCAG-AA contrast check.

Extract-to-component refactor

Click to expand

Right-click any element tree and the option “Luix: Extract to component…” appears which if clicked prompts for a name and then writes a new .luau alongside the source with only the imports the extracted code actually uses:

-- Before                              -- After (HomeScreen.luau)
local function HomeScreen()             local Card = require(script.Parent.Card)
    return e("Frame", { … }, {          local function HomeScreen()
        e("Frame", { -- cursor here         return e("Frame", { … }, {
            BackgroundColor3 = …,               e(Card, {})
            Size = UDim2.fromOffset(…),     })
        }, {                            end
            e("UICorner", { … }),
            e("TextLabel", { Text = "" })  -- Card.luau, freshly written:
        })                              local React = require(Packages.react)
    })                                  local e = React.createElement
end
                                        local function Card(props)
                                            return e("Frame", { … }, { … })
                                        end
                                        return Card

Framework-aware — emits e(Card, {}) for React/Roact and Card {} for Fusion/Vide.

Design tokens (palette, spacing, fonts)

Click to expand

Define tokens once in your settings; surface them as completions:

Setting Triggers after Suggests entries like
luix.palette Color3. palette.primary → Color3.fromRGB(124, 92, 255)
luix.spacing UDim. spacing.md → UDim.new(0, 16)
luix.fonts Font. fonts.display → Font.fromName("Gotham", …)

Plus a Save Color3 to palette code action on any literal so you build the palette as you go.

And more…

  • Inlay hints at closing brackets: }) ▸ Frame (Container) so you can see what just closed ten levels deep.
  • Outline + breadcrumbs reflect the element tree, not just Lua function structure.
  • N references CodeLens above every component definition.
  • Color3 format converter — swap between fromRGB / fromHex / new / fromHSV with one click.
  • UDim2 form converter — new / fromOffset / fromScale (when expressible).
  • Wrap-in code actions — Frame / ScrollingFrame / Frame + UIListLayout.
  • Component hover — hover e(MyButton, …) and see the inferred prop list + base class.
  • Wally + Rojo + scaffold actions in a dedicated activity-bar sidebar.

:high_voltage: Quick start

  1. Install from the Marketplace: Cmd/Ctrl + Shift + X → search Luix Roblox → Install.
    Or click: marketplace.visualstudio.com/items?itemName=ericplane.luix-roblox
  2. Open a .lua / .luau file and start typing inside an e(…) / New "…" {…} / create "…" {…} / `…` call. Suggestions surface automatically.
  3. (Optional) In settings, set luix.frameworks to only the frameworks you use. Default is all four.

That’s it — zero config required.


:hammer_and_wrench: Settings worth knowing

{
  // Pick which frameworks Luix recognizes
  "luix.frameworks": ["react", "roact", "fusion", "vide"],

  // Snippet format for Color3 placeholders
  "luix.color3.defaultFormat": "fromRGB", // fromRGB | fromHex | new | fromHSV
  "luix.richText.defaultColorFormat": "hex", // hex | rgb

  // Opt-in: thumbnail previews in the gutter
  "luix.imageGutter.enabled": false,
  "luix.imageGutter.cacheLocation": "global", // global | workspace

  // Visual editors + previews (all on by default — toggle off if undesired)
  "luix.gradient.codeLensEnabled": true,
  "luix.gradient.previewOnHover": true,
  "luix.rectEditor.codeLensEnabled": true,
  "luix.hoverPreviews.enabled": true,
  "luix.unusedProps.enabled": true,

  // Optional: paste a Roblox Open Cloud API key with the `legacy-asset:manage`
  // permission to auto-detect sprite source dimensions in the rect editor
  "luix.openCloud.apiKey": "",

  // Opt-in: sort props by category on every save (the code action is always available)
  "luix.sortProps.onSave": false,

  // Opt-in: WCAG AA contrast warnings
  "luix.contrastWarnings.enabled": false,

  // Opt-in: subtree-bloat CodeLens
  "luix.frameStatsLens.enabled": false,

  // Opt-in: workspace-wide diagnostic tally in the sidebar
  "luix.workspaceValidation.enabled": false,

  // Opt-in: augment built-in prop list from Roblox's API dump
  "luix.useRobloxApiDump": false,
}

Full settings list with descriptions in the README.


:scroll: Updates

  • v1.5.0 — Per-file framework detection (each file only surfaces its own framework’s snippets), active-framework status-bar item + luix.activeFramework override, Vide parens-form support (Vide.create("Frame", {…}) ), full four-framework parity (Roact scaffolds, matching element snippets, state primitives), workspace-wide rename (F2 ), UDim2.fromScale ↔ fromOffset conversion, Vide direct-call completions (Frame({…}) ), component auto-import, and completion-gating fixes.
  • v1.4.0 — Gradient editor (ColorSequence + NumberSequence + UIGradient with live preview, rotation, parent-BG-aware mixing), sprite-rect editor (drag-resize on the asset thumbnail, ScaleType. Crop preview, Open Cloud native-dimension auto-detect), visual hover previews for TweenInfo / UIPadding / UICorner / UIStroke, sort-props-by-category code action + opt-in format-on-save, unused-prop diagnostic, per-class prop type overrides (Frame.Style vs GuiButton.Style).
  • v1.3.0 — Design tokens (spacing/fonts), Color3-to-palette extractor, frame-stats CodeLens, project diagnostic summary, numeric-range + TextScaled + WCAG-contrast diagnostics, workspace index persistence, optional Roblox API-dump augmentation, CFrame.Angles snippets, opt-in class picker on (.
  • v1.2.0 — Custom-component hover, Color3 picker now recognizes fromHex + fromHSV, convert-Color3-format action, reference CodeLens, anchor presets, AnchorPoint auto-detect, wrap-in code actions, UDim2 form conversion, image-asset hover, extract-to-component, image gutter previews.
  • v1.1.0/1.1.1 — RichText tag completion + attribute chaining + auto-close + color picker, Roblox PUA glyph display + custom glyphs, prop-validation diagnostic suite (unknown/duplicate/wrong-enum/overridden), class-name completion in factory string args.

Full changelog: CHANGELOG.md.


:link: Links

48 Likes

I use Fusion a lot, so I’ll check this out whenever I start using external IDEs for Roblox again! Seems promising.

2 Likes

Peak extension, great work Eric!

2 Likes

That’s cool! I’ll definitely use this in my projects. Nice work.

1 Like

This is incredible, will 100% be integrating this into my workflow. Amazing.

3 Likes

this is the best thing ever! thanks for making this

1 Like

Just downloaded it, works great! I’m loving the image asset previewing, was just looking for something like this recently!

1 Like

Luix v1.4.0 - The Visuals Update

Two new full webview editors, four inline hover previews, a sort action, and a new diagnostic.

  • Gradient editor — drag color/transparency stops, slide rotation, live preview (mixes with the parent’s BackgroundColor3 like Roblox does). Works on UIGradient, ColorSequence, and NumberSequence.
  • Sprite-rect editor — drag-resize a rect on the asset thumbnail, dashed crop preview for ScaleType.Crop, scroll-to-zoom canvas. Optional Open Cloud API key auto-detects each asset’s native pixel size.
  • Hover previews — inline SVGs for TweenInfo.new(...) easing curves and e("UIPadding" / "UICorner" / "UIStroke", {…}) configs.
  • Sort props by category — right-click → reorder a props table into a specified order which can be adapted in settings. Opt-in format-on-save too.
  • Unused-prop diagnostic — declared in props: { … } but never read in the body? Greyed out like an unused local.
  • Per-class type overrides (Frame.Style vs GuiButton.Style finally resolve correctly), wrap-in indentation fix, dozens of perf tweaks.

Full changelog: CHANGELOG.md.

This extension is spectacular, thank you for making & maintaining it! I’m sure many would appreciate if this was uploaded to the Open VSX Registry so Cursor users can install it without having to manually download the VSIX from VS Code.

2 Likes

Thanks for the suggestion but after looking into it more I won’t be putting it onto the Open VSX Registry as I don’t agree with some of the information which will be made public on there. (i.e. Full Name). So sadly you will have to manually download the VSIX from VS Code for the moment.

1 Like

Luix v1.4.1 - Wally Hotfix

  • Fixed the Regenerate Wally Types button and luix.wally.regenerateTypes command so the chaining of the commands works correctly and there is a 1500 ms breather between wally install and rojo sourcemap since Windows Defender briefly locks the freshly written Packages/*.lua link files for real-time scanning and without this pause rojo’s sourcemap might not find the linker nodes.

Full changelog: CHANGELOG.md.

Luix v1.4.2 - Thumbnail/Gutter Hotfix

  • Fixed thumbnails getting stuck on “unavailable” for a full minute by no longer caching transient API states (Pending, InReview, Error, TemporarilyUnavailable).

Full changelog: CHANGELOG.md.

Luix v1.4.3 - Vide Hotfix

  • Added support for Vide’s bare-call shapes (Frame({...}), TextButton {...}, CustomComponent({...}))
  • Autocomplete for Size/Position (or any UDim2 prop) now inserts UDim2. so you can pick from .new/.fromScale/.fromOffset yourself instead of forcing .new(0, 0, 0, 0).

Full changelog: CHANGELOG.md.

I totally understand, thanks for the consideration! It’s not much of a problem to manually download and install the vsix file.

1 Like

Luix v1.4.4 - Rename & Polish Hotfix

  • Added workspace-wide component renaming which you can activate by pressing F2 on a component name and it renames its definition and every call site (e(Comp, ...), Comp({...}), etc) across the workspace.
  • Fixed some bugs and increased performance (i.e. completions and snippets no longer leak into strings, etc)

Full changelog: CHANGELOG.md.

Sensational, definitely going to find a use out of this. Good stuff!

1 Like

Luix v1.4.5 - UDim2 & Completion Fixes

  • Added two UDim2 refactor actions: convert fromScale ↔ fromOffset by checking the pixel size from the parent chain and calculating a parent’s Size from its children (Also is aware of UIPadding and UIListLayout, etc).
  • Rect editor X/Y positions (for ImageRectOffset) now accept negative values.
  • Workspace components now only surface in real UI files and no longer leak into server scripts or type X = declarations.
  • Component completions now auto-import the require path.

Full changelog: CHANGELOG.md.

Luix v1.5.0 - Per-file framework detection & parity improvements

  • Added per-file framework detection which lets Luix pick the framework per file (from its require calls), this means React files only surface React snippets, same with Vide, etc. No more cross-framework clutter.
  • New status-bar item shows the active framework that Luix found in that file; Clickable to override it per workspace, or simply set luix.activeFramework.
  • Added Vide parens-form support (Vide.create("Frame", {...})).
  • Full parity is now included for all four supported frameworks. Each now offer the same features for each framework. (i.e. Fusion Value, Computed, Spring, …) (i.e. Vide source, derive, effect, …)

Full changelog: CHANGELOG.md.

1 Like

Thank you for this, this extension has saved me so much time and headaches. Please keep developing it!

1 Like

very cool extension! But i have an issue where i have disabled auto imports, but im still getting auto import suggestions. Can this get fixed?


also i cant find a way to disable those hook snippets, is there a way to do that?

1 Like