{"id":15117110,"url":"https://github.com/nightcycle/synthetic","last_synced_at":"2026-01-26T22:35:31.586Z","repository":{"id":45075663,"uuid":"437388895","full_name":"nightcycle/synthetic","owner":"nightcycle","description":"A UI library with the the goal of porting the Google Material Design framework / philosophy to Roblox.","archived":false,"fork":false,"pushed_at":"2024-12-17T09:10:33.000Z","size":10925,"stargazers_count":31,"open_issues_count":9,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-27T22:33:08.133Z","etag":null,"topics":["cold-fusion","fusion","roblox","wally"],"latest_commit_sha":null,"homepage":"","language":"Luau","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nightcycle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-11T21:06:21.000Z","updated_at":"2025-06-13T02:23:26.000Z","dependencies_parsed_at":"2024-01-22T15:47:52.688Z","dependency_job_id":"50c428b2-fa54-42dd-be72-2b923bde52e2","html_url":"https://github.com/nightcycle/synthetic","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/nightcycle/synthetic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nightcycle%2Fsynthetic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nightcycle%2Fsynthetic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nightcycle%2Fsynthetic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nightcycle%2Fsynthetic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nightcycle","download_url":"https://codeload.github.com/nightcycle/synthetic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nightcycle%2Fsynthetic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28790115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T21:49:50.245Z","status":"ssl_error","status_checked_at":"2026-01-26T21:48:29.455Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cold-fusion","fusion","roblox","wally"],"created_at":"2024-09-26T01:45:50.508Z","updated_at":"2026-01-26T22:35:31.561Z","avatar_url":"https://github.com/nightcycle.png","language":"Luau","funding_links":[],"categories":["Components","Packages"],"sub_categories":["Synthetic \u003cimg src=\"luau.svg\" width=\"18px\" /\u003e\u003cimg src=\"roblox-ts.svg\" width=\"18px\" /\u003e","UI"],"readme":"# Synthetic\nSynthetic = Fusion created Material\n\nThis UI library is powered by a Fusion framework variant, and has the the goal of porting the Google Material Design framework / philosophy to Roblox.\n\n**Disclaimer:** Should not be used in a live game until Roblox releases [Flex functionality](https://devforum.roblox.com/t/new-uilistlayout-flex-features-beta/2694081)\n\nCurrently Supports:\n- Wrapper / No-Framework\n- [Fusion](https://github.com/dphfox/Fusion)\n- [Cold-Fusion](https://github.com/nightcycle/cold-fusion)\n- [Roact](https://github.com/Roblox/roact/)\n\u003c!-- - [React-Lua](https://github.com/Roblox/react-lua) --\u003e\nhttps://github.com/nightcycle/synthetic/assets/77173389/42044196-0b4a-4e76-9276-88edaf60ef55\n\nAll components can be previewed with the [Hoarcecat](https://github.com/Kampfkarren/hoarcekat) plugin. Anything named \"_Config\" showcases the different ways a component can be configured. Anything named \"_Theme\" showcases how the component looks when made with the more portable theme constructors.\n\n# Install\nDepending on the workflow you're using, you'll want to install a specific wally package:\n- [No Framework](https://wally.run/package/nightcycle/synthetic-wrapper)\n- [Roact](https://wally.run/package/nightcycle/synthetic-roact)\n- [Fusion](https://wally.run/package/nightcycle/synthetic-fusion)\n- [Cold-Fusion](https://wally.run/package/nightcycle/synthetic-cold-fusion)\n\nIf you plan to use multiple frameworks, you can install one containing all [here](https://wally.run/package/nightcycle/synthetic-roact). The one containing all the frameworks is what's used in most documentation.\n\n# Goals\n\nIt has been iterated heavily upon since it began in 2020, with its mission statement evolving over time.\n\n## Components Should Be Decoupled\nStylesheet solutions are inevitable, however until we have one that everyone can agree upon (so likely never), the components in this framework shouldn't require the developer buy into a whole ecosystem. If you just need a switch, or a slider, or whatever - you should be able to just take it.\n\nThat being said, stylesheet solutions exist for a reason. In the case of Synthetic the compromise was to have all root constructors (aka `.new(...)`) use only native Roblox parameters with the exception of a few simple immutable datatypes that can be created with a single function call.\n\nFrom there, quality-of-life functions that have far fewer parameters would all fully embrace the framework's styling solution. If this solution ever evolves / gets swapped out with a standardized solution, the base components shouldn't need much refactoring.\n\n## It Should Support All Major Workflows\nIn the past whenever I see a great UI library, I often find myself torn on whether to use it due to it not integrating cleanly into my workflow.\n\nI use a fairly niche variant of [Fusion](https://github.com/dphfox/Fusion), called [Cold-Fusion](https://github.com/nightcycle/cold-fusion). It's an opinionated Fusion wrapper meant to make things readable by moving away from the functional elegance of Fusion, and towards an OOP direction with memory leak protections in mind. This is the language I wrote Synthetic in.\n\nI've written interface `definition.json` files, allowing for the easy parsing of how the constructors are configured. From then I use a [python script](scripts/compile/run.py) to generate the appropriate ports for them.\n\n## Components Should Strive to Scale Dynamically\nThe Roblox UI native behavior is a bit messy, with many components with overlapping control fighting for the scale / position of elements on screen. When possible, I would prefer to not get involved in that battle.\n\nAs a result, the components are designed to allow for rescaling of the root instance as desired without skewing / breaking the component. There are limits - you can't just make an element infinitely small and expect it to remain usable.\n\n## Type Safety is Non-Negotiable\nI love fusion, but the table based method all the constructors use is a headache. There are of course workaround solutions such as [Fusion Autocomplete](https://github.com/VirtualButFake/fusion_autocomplete), however this feels like a band-aid solution to the underlying problem.\n\nI've gone and used parameter dense functions for constructors, relying on the parameter name hinting to guide me. In my experience, this reduces debugging turnaround and leads to faster implementation.\n\n\n# Components\n## Buttons\n- [Badge](./src/Component/Button/Badge/README.md)\n\n### Common\n- [Elevated](./src/Component/Button/ElevatedButton/README.md)\n- [Filled](./src/Component/Button/FilledButton/README.md)\n- [Outlined](./src/Component/Button/OutlinedButton/README.md)\n- [Text](./src/Component/Button/TextButton/README.md)\n\n### Icons\n- [Filled Icon](./src/Component/Button/FilledIconButton/README.md)\n- [Outlined Icon](./src/Component/Button/OutlinedIconButton/README.md)\n\n### FAB\n- [FAB](./src/Component/Button/FAB/README.md)\n- [Extended FAB](./src/Component/Button/ExtendedFAB/README.md)\n\n### Chips\n- [Assist Chip](./src/Component/Button/Chip/Assist/README.md)\n- [Filter Chip](./src/Component/Button/Chip/Filter/README.md)\n\n## Menu\n### Row\n- [Segmented](./src/Component/Menu/Row/Segmented/README.md)\n\n### Bar\n- [Bottom](./src/Component/Menu/Row/Bar/Bottom/README.md)\n- [Top Center](./src/Component/Menu/Row/Bar/Top/Center/README.md)\n- [Top Large](./src/Component/Menu/Row/Bar/Top/Large/README.md)\n- [Top Medium](./src/Component/Menu/Row/Bar/Top/Medium/README.md)\n- [Top Small](./src/Component/Menu/Row/Bar/Top/Small/README.md)\n\n## Text Field\n- [Filled](./src/Component/TextField/Filled/README.md)\n- [Outlined](./src/Component/TextField/Outlined/README.md)\n\n## Progress Indicator\n- [Circular](./src/Component/ProgressIndicator/Circular/README.md)\n\n## Snackbar\n- [Small](./src/Component/Snackbar/Small/README.md)\n- [Large](./src/Component/Snackbar/Large/README.md)\n\n## Search\n- [Filled](./src/Component/Search/Filled/README.md)\n- [Text](./src/Component/Search/Filled/README.md)\n\n## Misc\n- [Dialog](./src/Component/Dialog/README.md)\n- [Checkbox](./src/Component/Checkbox/README.md)\n- [Radio Button](./src/Component/RadioButton/README.md)\n- [Switch](./src/Component/Switch/README.md)\n- [Slider](./src/Component/Slider)\n\n\n# Style\nA lot of UI coding is assigning colors, fonts, and sizing - Synthetic attempts to fix that with a new immutable datatype: \"Style\". It's immutable due to its intention to be swapped out within states - a case where changes are much more easily detected when the datatype is immutable.\n\n## Usage\nConstruction:\n```luau\n-- construct style somewhere\nlocal style = Synthetic.Style.new(\n\t1, -- scale applied to all components\n\tEnum.Font.SourceSans -- Enum.Font or Typography type,\n\tif isDarkMode then Enums.SchemeType.Dark else Enums.SchemeType.Light, -- used for theme engine solver\n\tcolor -- the singular color the entire theme should be built around\n)\n````\n\nUsage:\n```luau\n\n-- here's a basic switch\nlocal primarySwitch = Synthetic.Components.Switch.ColdFusion.primary(\n\tstyle,\n\tfunction(isSelected: boolean)\n\t\tprint(\"is selected\", isSelected)\n\tend, -- onSelect\n\ttrue, -- initialSelection\n)\n\n-- here's a basic switch with all optional parameters\nlocal fullPrimarySwitch = Synthetic.Components.Switch.ColdFusion.primary(\n\tstyle,\n\tfunction(isSelected: boolean)\n\t\tprint(\"is selected\", isSelected)\n\tend, -- onSelect\n\ttrue, -- initialSelection,\n\ttrue, -- isEnabled\n\ttrue, -- includeIconOnSelected\n\ttrue -- includeIconOnDeselected\n)\n\n\n-- this constructor can be used without a style state - it's much more verbose / slower to implement\nlocal fullSwitch = Module.ColdFusion.new(\n\tfunction(isSelected: boolean?)\n\t\tprint(\"is selected\", isSelected)\n\tend, -- onSelect\n\ttrue, -- initialSelection\n\ttrue, -- isEnabled\n\ttrue, -- includeIconOnSelected\n\ttrue, -- includeIconOnDeselected\n\tstyle:GetColor(Enums.ColorRoleType.SurfaceContainerHighest), -- backgroundColor\n\tstyle:GetColor(Enums.ColorRoleType.Outline), -- onBackgroundColor\n\tstyle:GetColor(Enums.ColorRoleType.Primary), -- fillColor\n\tstyle:GetColor(Enums.ColorRoleType.PrimaryContainer), -- buttonColor\n\tstyle:GetColor(Enums.ColorRoleType.OnPrimaryContainer), -- onButtonColor\n\tstyle:GetColor(Enums.ColorRoleType.Surface), -- disabledColor\n\tstyle:GetColor(Enums.ColorRoleType.OnSurface), -- onDisabledColor\n\t1, -- elevation\n\tstyle.SchemeType, -- schemeType\n\tstyle:GetFontData(Enums.FontType.LabelLarge), -- fontData\n\tstyle.Scale -- scale\n)\n```\nA style type is mostly composed of two underlying immutable classes: Theme and Typography.\n\n## Theme\nTheme is inspired by the philosophy of the [Material Design color role system](https://m3.material.io/styles/color/roles), and powered by a manual port of their [open source color engine](https://github.com/material-foundation/material-color-utilities).\n\nThe theme engine is a bit slow due to all the crazy math (~10 ms construction), so it caches the results from prior constructions. This shouldn't be a problem if you're only constructing a handful of these and passing them around, however if you find yourself creating a new one from dynamic input multiple times a second be warned. From my own experience though, since I added caching I haven't noticed any notable performance issues.\n\n### Construction\nShould you want to define the theme manually, you can construct one and pass it as a parameter when creating the Style type.\n```luau\nlocal source: Color3 = Color3.new(1,0,0)\nlocal customPalette: {[string]: Color3} = {\n\tRobloxRed = Color3.fromHex(\"#E2231A\"),\n}\nlocal theme = Synthetic.Theme.new(\n\tsource,\n\tcustomPalette -- can be nil\n)\n```\n\n### Type Usage\n![example color roles](./media/color-roles.png)\nThe entire goal of the theme type is to replace hours tinkering with colors, and replace them with a few context relevant tokens. The theme type will then take those tokens and return the appropriate color.\n```luau\n-- using official color role\nlocal role = \"Primary\"\nlocal scheme = \"Light\"\nlocal elevation = 0 -- from (0-6) how much color is modified to appear close to the camera.\nlocal color: Color3 = theme:Get(\n\trole,\n\tscheme,\n\televation -- optional, will default to 0\n)\n\n-- using custom color role\nlocal customRole = \"RobloxRed\"\nlocal colorType: \"Custom\" | \"CustomContainer\" | \"OnCustom\" | \"OnCustomContainer\" = \"CustomContainer\"\n\nlocal customColor: Color3 = theme:GetCustom(\n\tcustomRole,\n\tcolorType,\n\tscheme\n\televation\n)\n```\n### Palettes\n![example palette](./media/tonal-palette.png)\nThe theme engine also generates palettes - functions that return a color with a consistent tone, but a brightness level dependent on the provided parameter. If you're interested in going deeper into color customization, you can do so with the palettes.\n\n```luau\nlocal tone: \"Primary\" | \"Secondary\" | \"Tertiary\" | \"Neutral\" | \"NeutralVariant\" | \"Error\" = \"Primary\"\nlocal isClampEnabled = true -- bright white and pitch black lack a tone, so on default it clamps the values to visually distinct ranges\nlocal brightness = 0.7\nlocal color = style.Palettes[tone](\n\tbrightness,\n\tisClampEnabled -- optional, defaults to true\n)\n```\n\n### Interface Usage\nIf you just want to quickly solve a color's elevation, you can do that without constructing a theme.\n```luau\nlocal sourceColor: Color3 = Color3.new(1,0,0)\nlocal elevation: number = 3\nlocal scheme = \"Light\"\nlocal elevatedColor = Synthetic.Theme.getElevatedColor(sourceColor, elevation, scheme)\n```\n\n## Typography\nThe typography half of the style type is fairly simple. It's basically a dictionary of tokenized use cases as the keys, and FontData as the values.\n\n### FontData\nFontData is the immutable data structure that all text appearance related state is stored.\n```luau\ntype FontData = {\n\tFont: Font, -- the font to apply to the text\n\tSize: number, -- the pixel height of the typical character\n\tTracking: number, -- the letter spacing ratio. 1.0 = monospaced\n\tLineHeight: number, -- the pixel height of the line + the gap separating it from a neighboring line\n}\n\nlocal fontData = Synthetic.Types.FontData.new(\n\tFont.fromEnum(Enum.Font.Arial),\n\t12,\n\t0.4,\n\t14\n)\n\n```\nThis data is much easier to pass around across functions. It's used heavily internally, however you're welcome to use it externally if you like.\n\n### Construction\nTo create a custom typography off of a Roblox font, you can do so like this:\n```luau\nlocal font = Font.fromEnum(Enum.Font.Arial)\nlocal typography = Synthetic.Typography.fromFont(font)\n```\n\nIf you want something more custom, you'll have to define all of the font-data manually\n```luau\nlocal typography = Synthetic.Typography.new({\n\tDisplayLarge = Synthetic.Types.FontData.new(\n\t\tFont.fromEnum(Enum.Font.Arial),\n\t\t12,\n\t\t0.4,\n\t\t14\n\t),\n\tDisplayMedium = Synthetic.Types.FontData.new(\n\t\tFont.fromEnum(Enum.Font.Arial),\n\t\t10,\n\t\t0.35,\n\t\t12\n\t),\n\t-- etc\n})\n```\n\n### Usage\nThe goal of typography is to quickly provide fontData.\n```luau\nlocal fontData = typography:Get(\"DisplayLarge\")\n```\n\n### Manual Application\nIf you aren't sending the fontData as a constructor parameter, you can apply it yourself.\n```luau\nlocal fontData = typography:Get(\"BodySmall\")\nlocal scale = 1\n\nlocal textLabel = Instance.new(\"TextLabel\")\ntextLabel.TextSize = Synthetic.Typography.getTextSize(fontData.Size, scale),\ntextLabel.LineHeight = Synthetic.Typography.getGuiLineHeight(fontData.LineHeight, fontData.Size)\ntextLabel.FontFace = fontData.Font\n\n-- or, if it's just the main text property\nSynthetic.Typography.apply(\n\ttextLabel,\n\tfontData,\n\tscale -- scale is optional, defaults to 1\n)\n\n-- alternatively if you have a Typography type around\ntypography:Apply(\n\ttextLabel,\n\t\"BodySmall\",\n\tscale -- scale is optional, defaults to 1\n)\n```\n\n# Sounds\nI've gone and uploaded [all the sounds](https://m2.material.io/design/sound/sound-resources.html) made open source by an older version of the Material Design site. They're available as a static dictionary with keys taken from the original file names. They're used throughout the components, however you're welcome to use them as well!\n\n# Icons\nAll of the google icons are [imported as spritesheets](https://github.com/nightcycle/material-icons), and are recommended to use with this library. They natively support the ImageData type, and are just handy to have around.\n\n\n# Utility Components\nIn making this framework, various patterns became apparent. To save code frequent instance use-cases were organized into internal components. You can access them under `Synthetic.Util`.\n\nThey're not really meant for developers, however you might find them handy. Currently these aren't translated into other frameworks, however if this is something people find handy then I'll definitely fast-track it.\n\n## Containers\nThe container interface helps quickly construct invisible frames that scale with the contents. Very handy when dealing with flex hierarchies.\n\n## ImageLabel\nAllows for the quick construction of basic icons and images using the ImageData format\n\n## List\nVarious list constructors, typically more focused on dealing with flex logic.\n\n## Padding\nQuickly add scale considerate UIPadding to instances.\n\n## ScrollingContainer\nA set of scrolling container constructors that tries to fit whatever you put into it, disappearing into a container if everything fits at once.\n\n## TextLabel\nQuickly add labels to things. Includes built in support for icons at beginning / end of text.\n\n# Transitions\nThe Material Design philosophies on [motion](https://m3.material.io/styles/motion/overview) is a very interesting read - however it's not one that is currently hooked up to most components. However if you do want their expertly curated theories on motion in your game, the Transitions utility may help you.\n\n# Contributing\nIf you like this framework, please feel free to contribute!\n\n## Bug Fixes / Optimizations / Refactors\nDefinitely feel free to make pull requests for bug fixes, for the most part I've got no issue with those so long as they don't change any APIs or expected behaviors.\n\n## New Components\nPlease make an issue before you start working on anything new. For the most part any [Material Design Components](https://m3.material.io/components) are fair game. Please rely on the specs sections of each component to make sure the port properly translates the majority of functionality.\n\nThat being said - I recognize Material Design is tackling a slightly different problem than game development, so some components such as proximity prompts, color pickers, etc just don't exist. In the cases where the component is a clear upgrade, I am more than happy to allow for its addition.\n\nNew components can be written in non [Cold Fusion](https://github.com/nightcycle/cold-fusion) frameworks, however if you can stomach it, using Cold Fusion is preferred as we want to avoid adding too many translation layers for performance reasons. If you do a fusion variant language, the public facing parameters should all be a `CanBeState\u003cV\u003e` type.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnightcycle%2Fsynthetic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnightcycle%2Fsynthetic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnightcycle%2Fsynthetic/lists"}