# Composition

Composition is another fundemental concept in OOP that combines objects or data types into more complex ones. This implements a _has-a_ relationship between objects, rather than _is-a_ from [Inheritance](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/advanced/inheritance/).

For example, let's imagine a coffee machine. This coffee machine **has a** grinder, a brewer, and many other components, but the coffee machine itself _is not one of them_.

    local GroundCoffee = class "GroundCoffee" {}
    local Coffee = class "Coffee" {}

    local Grinder = class "Grinder" {
        Public = {
            grind = function(self)
                -- Grinds the coffee and returns a new GroundCoffee
                return GroundCoffee.new()
            end,

    local BrewingUnit = class "BrewingUnit" {
        Public = {
            brew = function(self, groundCoffee)
                -- Brew the groundCoffee and return a new Coffee.
                return Coffee.new()
            end,

    local CoffeeMachine = class "CoffeeMachine" {
        constructor = function(self)
            self.Grinder = Grinder.new()
            self.BrewingUnit = BrewingUnit.new()
        end,
        Public = {
            brewCoffee = function(self)
                -- Brew the coffee using the grinder and the brewing unit of the object, and return it.
            end,
        },
        Private = {
            Grinder = false,
            BrewingUnit = false
            -- Add other members to create a full coffee machine

In the example above, we have `GroundCoffee` and `Coffee` classes which are used within the `Grinder` and `BrewingUnit` classes, and the `CoffeeMachine` class that uses them. As you can see in the example, `CoffeeMachine` only exposes a `brewCoffee` method, that brews a `Coffee` and returns it. (Implementation details are skipped for the sake of clarity.)

Like we have discussed in the [Encapsulation](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/best-practices/encapsulation/) page, by restricting access through only exposing a method to brew a coffee, we allow for data integrity, so we can be sure that `CoffeeMachine`'s `Grinder` and `BrewingUnit` objects will not change, and they cannot be accessed directly.

Maintainability is also increased, as if the implementation of the `Grinder`'s `grind` method ever changes, `CoffeeMachine` will not be affected. The same can be said for `BrewingUnit` as well.

## Composition Over Inheritance

Like we have mentioned in the [Inheritance](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/advanced/inheritance/) page, inheritance is a powerful way to achieve code reuse, but not always it is the best tool for the job.

Inheritance may sometimes violate encapsulation. A derived class depends on the implementation details of its base class, so if the base class gets a change in its implementation, the derived class may break, even though its code hasn't been touched. Due to this, the derived class must also be updated alongside the base class, to ensure it works correctly.

    local User = class "User" {
        Public = {
            login = function(self)
                print("User logged in.")
            end

    local Admin = class "Admin" (User, nil) {
        Public = {
            manageUsers = function(self)
                print("Admin managing users.")
            end

    local Editor = class "Editor" (User, nil) {
        Public = {
            editContent = function(self)
                print("Editor editing content.")
            end

    local Viewer = class "Viewer" (User, nil) {
        Public = {
            viewContent = function(self)
                print("Viewer viewing content.")
            end

In the example above, we have created a user hierarchy, where different types of users have different methods.
The problems with this system, however are:

  * If we want to create a new role, or a new ability, we need to create a new class.
  * If certain roles share the same functions, it might even decrease code reusability.
  * If we want to create a user with multiple roles, such as an `Admin` whos also an `Editor`, the system becomes even more complicated.

However, if we use composition over inheritance:

    local Permission = class "Permission" {
        Public = {
            execute = function(self)
                -- Execute the permission, can be overwritten.
            end

    local ManageUsers = class "ManageUsersPermission" (Permission, nil) {
        Public = {
            execute = function(self)
                print("Managing users.")
            end

    local EditContent = class "EditContentPermission" (Permission, nil) {
        Public = {
            execute = function(self)
                print("Editing content.")
            end

    local ViewContent = class "ViewContentPermission" (Permission, nil) {
        Public = {
            execute = function(self)
                print("Viewing content.")
            end

    local User = class "User" {
        constructor = function(self, username)
            self.Username = username
        end
        Public = {
            addPermission = function(self, permission)
                table.insert(self.Permissions, permission)
            end,
            login = function(self)
                print(self.Username + " logged in.")
            end,
            executePermissions = function(self)
                for _, permission in self.Permissions do
                    permission:execute()
                end
            end
        },
        Private = {
            Username = "",
            Permissions = {}

In the example above, instead of mainly using inheritance, we used composition. In other words, instead of extending the users to create new classes, we extended permissions, that each can have different abilities and methods. This allowed us to create a very flexible `User` class that can have multiple permissions.

## Tips

Like you've seen in the examples above, composition over inheritance generally yields better results. However, this does not mean that inheritance holds no place in OOP, as in the above example when we created our permission classes, we still used inheritance.

This phrase from ["Effective Java"](https://kea.nu/files/textbooks/new/Effective%20Java%20\(2017%2C%20Addison-Wesley\).pdf) summarizes where you should use composition and inheritance very well: _"...a class B should extend a class A only if an “is-a” relationship exists between the two classes. If you are tempted to have a class B extend a class A, ask yourself the question: Is every B really an A? If you cannot truthfully answer yes to this question, B should not extend A. If the answer is no, it is often the case that B should contain a private instance of A and expose a different API: A is not an essential part of B, merely a detail of its implementation."_

Back to top


#  checkInheritance :[boolean](https://create.roblox.com/docs/luau/booleans)

    function Util.checkInheritance(class: class, classOrMethod: class | () -> ()): boolean

If the `classOrMethod` is a `class`, the function checks if the given `class` has been inherited from `classOrMethod`, or if the `classOrMethod` is a method, then it simply checks if any of the classes that the given `class` inherits from contains the method.

## Default syntax

    local isInherited = ClassPP.Util.checkInheritance(classOne, classTwo) -- For classes
    local isInherited = ClassPP.Util.checkInheritance(class, method) -- For methods

## Parameters

class: [class](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The `class` that the check will be made from.
classOrMethod: [class | () ->()](https://tenebrisnoctua.github.io/ClassPP/latest/api-reference/data-types/class) | The `class` or the `method` that will be checked.

## Returns

[boolean](https://create.roblox.com/docs/luau/booleans)


# Friend Access Specifier

Sometimes, you may wish to access the private members of a class without having to use a class method. Perhaps for a utility or a library function, you may wish to grant them access to the private members of your class. This is where the friend access specifier comes in.

The friend access specifier allows all functions, and even other classes, defined inside itself to access the private members of a class.

Warning

Members of this access specifier will not be replicated to objects, rather, these members are stored in the class itself.

    local function getLicensePlate(object: any)
        print(object.License_Plate)
    end

    local class = ClassPP.class

    local Car = class "Car" {
        Public = {
            Brand = "Lamborghini",
        },
        Private = {
            License_Plate = "XXXX"
        },
        Friend = {
            getLicensePlate

    local newCar = Car.new()
    getLicensePlate(newCar)

In this example, we have a "Car" class that contains a private member called `License_Plate`, and a local function called `getLicensePlate`. By passing the reference of the `getLicensePlate` function to inside the friend access specifier, this function is now able to access the private members of the "Car" class.

Info

Besides functions, friend access specifier can include other classes as well. Classes can be saved by using their variables, or their names. You can put a string inside the friend access specifier that contains a class's name, and it will still work!

Back to top


# Operator Overloading

Suppose we have two objects that belong to a custom Vector class, how would we allow the addition of those two objects? Using a normal class method would suffice, but it would require you to call the same method over and over again every single time you want to add them together.

To solve this issue, just like in other object oriented languages, we can change the way operators work for our classes. This is known as operator overloading.

    local class = ClassPP.class

    local Vector = class "Vector3" {
        constructor = function(self, x: number, y: number, z: number)
            if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then self.coordinates = {0, 0, 0} return end
            self.coordinates = {x, y, z}
        end,
        Public = {
            coordinates = {0, 0, 0},
        },

    function Vector.Public:operator_add(otherVector)
        assert(#self.coordinates == #otherVector.coordinates)

        local coordinates = {}

        for i = 1, #self.coordinates do
            coordinates[i] = self.coordinates[i] + otherVector.coordinates[i]
        end

        return Vector.new(coordinates[1], coordinates[2], coordinates[3])
    end

    function Vector.Public:__tostring()
        return "(" .. table.concat(self.coordinates, ", ") .. ")"
    end

    local vector1 = Vector.new(4, 5, 2)
    local vector2 = Vector.new(1, 2, 3)

    print(vector1 + vector2) -- Prints "(5, 7, 5)"

In this example, we have created a custom Vector3 class that has a special function called `operator_add`, this special function is one of the special functions in Class++ that allows you to overload a specific operator. Here, we overloaded the operator `+` with our custom function that allows us to add two Vectors together.

## Overloadable Operators

Method | Description
`operator_add(self, obj)` | Function for overloading the + operator.
`operator_sub(self, obj)` | Function for overloading the - operator.
`operator_mul(self, obj)` | Function for overloading the * operator.
`operator_div(self, obj)` | Function for overloading the / operator.
`operator_idiv(self, obj)` | Function for overloading the // operator.
`operator_mod(self, obj)` | Function for overloading the % operator.
`operator_pow(self, obj)` | Function for overloading the ^ operator.
`operator_unm(self)` | Function for overloading the unary – operator.
`operator_eq(self, obj)` | Function for overloading the == operator.¹
`__tostring()` | Fired when tostring is called on the object.

Info

¹: This function will only run when == operator is used with the same base type (table, userdata, etc.). It will not work with different types, such as an object with a table, or another type.

Warning

Trying to directly call the operator functions will result in an error. They must be called with their operators.

Back to top


# Getting Started

Welcome to the Class++ tutorial section! In here, you will learn how to use and build systems with Class++.

## What You Should Know

While you don't need to be an expert programmer to use Class++, you should still have:

  * Basic knowledge of Luau.
    * While not a requirement, you should also have basic knowledge of [metatables](https://create.roblox.com/docs/luau/metatables).
  * Basic understanding on the concept of OOP (Object Oriented Programming).

Some tutorials may seem harder or easier depending on your existing knowledge. It is recommended that you follow each one in order, and try the given examples on your own.

## Installation

### Installing via Roblox

If you are creating experiences in Roblox, and you wish to install Class++, you can either:

  * Download the `.rbxm` file that contains the source code from the [latest release](https://github.com/TenebrisNoctua/ClassPP/releases/latest).

    * 1: In Roblox Studio, right click the location (e.g, `ReplicatedStorage`) that you wish to insert this file to.
    * 2: Go to "Insert > Import Roblox Model".
    * 3: Select the `ClassPP.rbxm` file that you've just downloaded.
  * Get the source code from [Roblox Creator Store](https://create.roblox.com/store/asset/18312821151/Class).

    * 1: In Roblox Studio, open Toolbox.
    * 2: Go to "Inventory > Models".
    * 3: Select the "Class++" item on your inventory. It should automatically be inserted into `Workspace`.

### Installing as Source Code

If you're using pure Luau, or if you're just synchronising external files into Roblox Studio, then you can use the source code instead.

To do this, you can either:

  * From the [latest release](https://github.com/TenebrisNoctua/ClassPP/releases/latest), download the source code as zip.

    * 1: Inside the zip, copy the `src` folder.
    * 2: Paste this folder into wherever you please, for example, inside another folder named `lib` or `shared`.
    * 3: Rename this pasted `src` folder as "ClassPP".
  * From the [wally package manager](https://wally.run/package/tenebrisnoctua/classpp).

    * 1: Copy the code on the "Install" section.
    * 2: Inside your `wally.toml` file, paste this code under the "dependencies" section.
    * 3: Run `wally install` through the command line.

## Testing

To make sure if Class++ has been successfully installed or not, you can create a simple script to test.

### Testing from Roblox

  * 1: Create a `Script` or a `LocalScript` instance, and parent it to `workspace` or `StarterPlayer > StarterPlayerScripts`.
  * 2: Remove the `print("Hello World")` line, and paste the following code in:

    local ReplicatedStorage = game:GetService("ReplicatedStorage") -- If you've inserted it to somewhere else, you can change this line.
    local ClassPP = require(ReplicatedStorage.ClassPP)
    local class = ClassPP.class

  * 3: Press "Play" or "Run". If everything is working properly and there are no errors, you're good to go!

### Testing from Source Code

If you're using the source code, you can `require()` Class++ in one of the following ways:

    -- Rojo (Roblox Instance)
    local ClassPP = require(ReplicatedStorage.ClassPP)

    -- Vanilla Luau (Using Require By String)
    local ClassPP = require("../shared/ClassPP")

Back to top


# Polymorphism

In simple terms, Polymorphism stands for "many forms", it's one of the core concepts of OOP. It refers to situations where something occurs in several different forms, and describes the concept that you can access objects of different types through the same interface.

[Function Overloading](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/advanced/functionOverloading/), [Operator Overloading](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/advanced/operatorOverloading/), and [Inheritance](https://tenebrisnoctua.github.io/ClassPP/latest/tutorials/advanced/inheritance/) belong to this concept, for example with inheritance, we can create derived classes that have the same method name, but all do different things. Or with function overloading, every function has different arguments and does a different thing, but all share the same name. It provides flexibility, and extensibility.

## Inheritance Example

    local class = ClassPP.class

    local Animal = class "Animal" {
        Public = {
            animalSound = function(self)
                print("The animal makes a sound")
            end

    -- Derived class
    local Pig = class "Pig" (Animal, nil) {
        Public = {
            animalSound = function(self)
                print("The pig says: oink oink")
            end

    -- Derived class
    local Dog = class "Dog" (Animal, nil) {
        Public = {
            animalSound = function(self)
                print("The dog says: woof woof")
            end

## Function Overloading Example

    local class = ClassPP.class

    local Test = class "Test" {}
    Test.overload("Public", "Display", {
        function(self, number1)
            print("Number: ", number1)
        end,
        function(self, number1, number2)
            print("Numbers: ", number1, number2)
        end
    })

Back to top


# frktest

A basic unit test framework for standalone Luau.

I developed this mostly for myself at a time where there were no alternatives
for Lune. I plan to keep it simple and use it for my projects that dont require
a heavy framework.

If you require more than basic test running, assertions, and console reporting,
consider using a larger framework (like
[jest-lua](https://github.com/jsdotlua/jest-lua) when it gets ported to lune,
see [issue](https://github.com/jsdotlua/jest-lua/issues/2) for tracking)

### Features

- It runs tests
- Pretty ergonomic to write (I think so)
- Assertion library, including table & array checks with diff printing, should_error, and a few more!
- Builtin console reporter with **colors**
- (undocumented) api to implement custom reporters (see [console_reporter.luau](src/reporters/console_reporter.luau) for sample use)

### Runtimes

frktest is runtime-agnostic. While any runtime should work, output colors
require runtime-specific implementations, currently the following runtimes have
output colors:

- [lune](https://github.com/lune-org/lune)
- [zune](https://github.com/Scythe-Technology/zune)
- [lute](https://github.com/luau-lang/lute)

### What does it look like tho?

Here's what a test looks like:

```lua
return function()
    test.suite("A collection of tests", function()
        test.case("A test that passes", function()
            local exp = 4
            local got = 2 + 2
            check.equal(got, exp)
        end)

        test.case("A test that fails", function()
            local exp = 3
            local got = 2 + 2
            check.equal(got, exp)
        end)

        test.case("Halt early", function()
            local a = nil
            req.not_nil(a)
            check.falsy(a.b) -- wont run, a failed req stops the test early
        end)

        test.case("Custom messages!", function()
            check.equal(1, 2, msg("my message prints in addition to the expansion"))
        end)

        test.case("Array assertions", function()
            local a = { "a", "b", "c" }
            local b = { "a", "z" }
            check.array.contains(a, { "z", "x" })
            check.array.equal(a, b)
        end)

        test.case("Table assertions", function()
            local a = { a = 1, b = 2, c = { a = 1, b = 2 } }
            local b = { a = 1, b = 22, c = { a = -1 } }
            check.table.contains(a, "b")
            check.table.equal(a, b)
        end)
    end)

    -- you can use test.focus to temporarily focus on a test case/suite:
    test.focus.suite("only this suite will run", function() end)
    test.focus.case("only this case will run", function() end)

    -- you can use test.skip to skip test cases/suites:
    test.skip.suite("all tests in this suite will be skipped", function() end)
    test.skip.case("this test will be skipped", function() end)
end

And here is the output (theme is [Rose Pine](https://github.com/rose-pine)):

![image](https://github.com/itsfrank/frktest/assets/7297152/f20a58d6-8e61-4635-893c-b4721ed9f3c9)

### How do I use it

#### Set up your environment

**Installing**

frktest is available on [wally](https://wally.run/), [pesde](https://docs.pesde.daimond113.com/), or you can just clone the repo.

using wally

Add the dependency:

in `wally.toml`:

```toml
[dev-dependencies]
frktest = "itsfrank/frktest@0.0.2"

Create alias in `.luaurc`:

```jsonc
  "aliases": {
    "frktest": "DevPackages/_Index/itsfrank_frktest@0.0.2/frktest/src",

using pesde

in `pesde.toml`:

```toml
[dev_dependencies]
frktest = { name = "itsfrank/frktest", version = "^0.0.2" }

Create alias in `.luaurc`:

```jsonc
  "aliases": {
    "frktest": "luau_packages/.pesde/itsfrank+frktest/0.0.2/frktest/src/"

**Note**: If you want to use the generated luau file in `./luau_packages`, in
the examples, replace `require("@frktest/frktest")` with
`require("./luau_packages/frktest")`. Reporters will be avilable in the
`_reporters` member:

```luau
-- from project root
local frktest = require("./luau_packages/frktest")
local console_reporter = frktest._reporters.console_reporter

A sample pesde project using frktest can be found here: <https://github.com/itsfrank/frktest-pesde-sample>

clone the repo

```shell
# somewhere on your machine
git clone https://github.com/itsfrank/frktest.git

Create alias in `.luaurc`:

```jsonc
  "aliases": {
    "frktest": "",

All example assume you created a `require` alias `"@frktest"` in your project's `.luaurc`:

```jsonc
  "aliases": {
    "frktest": "<see install sections above for paths>",

**note:** you should also register the alias with your lsp of choice, with `luau-lsp` using VSCode I think it would look like this:

```json
"luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": {
    "@frktest/": ""

#### Running tests

The framework does not have a cli or test discovery. So you need to make an entrypoint file, see [examples/run/default.luau](examples/run/default.luau) for how I made mine. But basically all you need is this:

```lua
-- filename: _run.luau

-- require test files and call the returned function
require("./some_test")()

-- initialize a reporter (there is currently only one... this one, but you can make your own!)
local console_reporter = require("@frktest/reporters/console_reporter")
console_reporter.init()

-- run the tests
local frktest = require("@frktest/frktest")
frktest.run()

Then you can run this file with lune like so:

```shell
> lune run _run.luau

#### Writing tests

I suggest you use these requires (but you do what you want!):

```lua
local frktest = require("@frktest/frktest")
local test = frktest.test
local check = frktest.assert.check
local req = frktest.assert.require

A test file should return a function, and for any tests to run the function
needs to create tests with `test.case`. If you want to group tests together you
can use `test.suite` and create tests inside of that. See [examples](examples)
for how I wrote a bunch of test files.

```lua
return function()
    test.case("a global test", function()
        -- ...
    end)

    test.suite("a group of tests", function()
        test.case("a test within a suite", function()
            -- ...
        end)
    end)
end

#### Assertions

Every single assertion has at least one example in the [examples](examples)
folder. If there are any nuances, I added comments.

Really though, they should be self explanatory, your LSP should list them all
out if you type `assert.check.|`

### End stuff

#### TODO

- Write tests to test the framework (ironic...)
- Encapsulate test state so that I can test the framework using the framework (with 2 separate states, one doing the testing, and one being tested)
- Probably add more assertions
- Fix bugs people might report

#### Acknowledgements

- [martinfelis/luatablediff](https://github.com/martinfelis/luatablediff) for a great table diff inplementation that I adapted for the `table.equal` assertions
- [kikito/inspect](https://github.com/kikito/inspect.lua) because it's the best pretty printer and I use it to print tables
- [catch2](https://github.com/catchorg/Catch2) inspired the test output and probably the assertion syntax

#### What's up with the name?

My name is Frank, I often use `frk` as a namespace/prefix for stuff meant for me :)


# intellij-luau

## Description

This plugin adds support for [Luau language](https://luau.org)

- Luau LSP integration ([Only for IDEs with LSP support](https://plugins.jetbrains.com/docs/intellij/language-server-protocol.html#supported-ides))
- StyLua: format action and ability to use it instead of builtin-in IDEA formatter
- Basic syntax highlighting/completion when LSP is disabled

Plugin is in development, feel free to request features and suggest changes in [Issue tracker](https://github.com/AleksandrSl/intellij-luau/issues).

## Getting started

### Supported file extensions

Plugin works both with `.luau` and `.lua` and may conflict with other Lua plugins.

### Install the plugin

- Launch the IDE and open the plugin settings at `File | Settings | Plugins`
- Search for "Luau" and install it

### Configure the plugin

By default, LSP is enabled if [supported](https://plugins.jetbrains.com/docs/intellij/language-server-protocol.html#supported-ides) by the IDE.
Once you open any Luau file, you will be prompted to download the latest version of the LSP (it will be installed into the IDE service directory).

If you want to configure a specific version of the LSP go to `File | Settings | Languages & Frameworks | Luau`.

By default `sourcemap.json` is used to enhance autocompletion.
There are several ways to autogenerate it as in the VSCode plugin.
If you have `default.project.json` and `rojo` installed, you'll be prompted to enable rojo autogeneration.
If you have a custom command to run, you can configure sourcemap generation in the same settings.

If you want to make settings default for the rest of the projects, there is an "apply and save as default" button at the end of the settings page.
Once you saved the default configuration, you will be prompted to apply them when opening a new project.

[LSP docs](https://github.com/JohnnyMorganz/luau-lsp)

## Installation

- Using the IDE built-in plugin system:

  Settings/Preferences > Plugins > Marketplace > Search for "intellij-luau" >
  Install

- Manually:

  Download the [latest release](https://github.com/AleksandrSl/intellij-luau/releases/latest) and install it manually using
  Settings/Preferences > Plugins > ⚙ > Install plugin from disk...

Plugin based on the [IntelliJ Platform Plugin Template][template].

[template]: https://github.com/JetBrains/intellij-platform-plugin-template
[docs:plugin-description]: https://plugins.jetbrains.com/docs/intellij/plugin-user-experience.html#plugin-description-and-presentation

## Acknowledgements

- IntellijElm, IntellijHaxe, and Intellij plugins for a ton of helpful examples and good code comments.
- Luau LSP for existence.

## Questions

### Why not add this to existing lua plugins?
The primary reason is that I wanted the thing to work as quickly as I can.
Adding support for somewhat different grammar and not breaking something that is already there sounded tricky to me.
But if there is an interest in the plugin in the future, maybe stuff can be ported.

Also, the builtin LSP support requires the most recent IDEA version.

### Why is `.lua` a supported file extension?
The codebase I started working on has all the files with `.lua` instead of proper `.luau` for unknown reason.


# Platinum

## Usage
```luau
local deserializer = require("deserializer")

local bytecode_buffer: buffer = buffer.fromstring(...)

local bytecode_deserializer = deserializer.new(bytecode_buffer)
print(bytecode_deserializer)


# HTMLuau

Simple HTML templating in Luau.

```luau
html({ lang = "en" }) {
    head() {
        title() {
            "Hello World",
        },
    },
    body() {
        h1() {
            "Hello World",
        },
        button({ onclick = "alert('Hello World!')" }) {
            "Click Me",
        },
        p() {
            "This is a simple HTML page generated using Luau.",
            "Lines are concatenated.",
            "So you can write sentences on separate lines.",
            "<html> is properly excaped.",
        },
        button(),
    },


<source media="(prefers-color-scheme: dark)" srcset="assets/cascade_span_dark.png">
    <source media="(prefers-color-scheme: light)" srcset="assets/cascade_span_light.png">

[![Stars][badges/stars]][stars]
[![Latest Release][badges/lastrel]][lastrel]
[![Last Modified][badges/lastcom]][lastcom]
[![Publish docs][badges/publish]][publish]

[![Discord][badges/disc]][disc]

Cascade is a Luau UI library based on macOS Sequoia.

![Cascade](assets/cascade_show_sequoia.png)

[stars]: https://github.com/biggaboy212/Cascade/stargazers
[lastrel]: https://github.com/biggaboy212/Cascade/releases/latest
[lastcom]: https://github.com/biggaboy212/Cascade/commits
[publish]: https://github.com/biggaboy212/Cascade/actions/workflows/publish.yml
[disc]: https://discord.gg/2cB4vBAEWk
[badges/stars]: https://img.shields.io/github/stars/biggaboy212/Cascade?label=Stars&logo=GitHub
[badges/lastrel]: https://img.shields.io/github/v/release/biggaboy212/Cascade?include_prereleases&label=Latest%20Release
[badges/lastcom]: https://img.shields.io/github/last-commit/biggaboy212/Cascade?label=Last%20Modifed
[badges/publish]: https://img.shields.io/github/actions/workflow/status/biggaboy212/Cascade/.github%2Fworkflows%2Fpublish.yml?label=Publish%20Docs
[badges/disc]: https://img.shields.io/discord/1384338360012898406?&label=Discord

## The Pitch

Cascade is a retained-mode, user-centric UI library designed to be both easy for developers and beautiful for users. Its API is fully typed, so once you learn one component, you can use 99% of the rest without needing to reference the documentation.

Cascade was designed from the ground up as a superior design paradigm. It combines simplicity and customizability into a high level bloat-free API, enabling developers to build polished software-grade UIs in minutes.

## Usage

Reference the [Cascade documentation](https://biggaboy212.github.io/Cascade/) for more.

- [Importing the Library](https://biggaboy212.github.io/Cascade/Getting%20Started/importing-the-library/)
- [The API](https://biggaboy212.github.io/Cascade/Getting%20Started/the-api/)
- [Components](https://biggaboy212.github.io/Cascade/Components/)

## Supporting the project

If you'd like to support the project, consider donating via any of these:

- **LTC**: `Lcd6pCPiVw9GH2MAaGkR8TX39EtL2LzFoa`
- **SOL**: `EfwivXazedEw2kL2et92NKDkT3qrQ6pdxA8Bs7RKYRuN`
- **ETH**: `0x85C09A302CBB42a321E737E6E4dDAC0Eebc4ac3E`
- **BTC**: `bc1qlhym0f0lejwlhd47gext8g7fgej366j7z85vvx`


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Getting Started/importing-the-library.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Getting Started/importing-the-library.md "View source of this page")

# Importing the Library

Note

You can clone the entire Cascade repo into your project with something _similar_ to wally, but there isn't currently a package manager that I know of which smoothly works with non roblox-dependent projects.

## Pre-built releases

Cascade also has pre-built `luau` modules.

### Over HTTP

This method will download the release dynamically using HttpGet.

    local function import(owner, repo, version, file)
        local tag = (version == "latest" and "latest/download" or "download/"..version)

        return loadstring(game:HttpGetAsync(("https://github.com/%s/%s/releases/%s/%s"):format(owner, repo, tag, file)), file)()
    end

    local cascade = import("biggaboy212", "Cascade", "latest", "dist.luau")
    -- If you want to use a specific release (i.e, beta releases), replace 'latest' with it's release tag.

### Local Build

  1. Download a valid release: [Cascade Releases](https://github.com/biggaboy212/Cascade/releases)
  2. Place the `luau` module into your project (e.g., under `packages/`).

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/index.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/index.md "View source of this page")

# Welcome

Welcome to the official API documentation for the [Cascade](https://github.com/biggaboy212/Cascade) UI Library. You can use these docs as reference to build interfaces with the Cascade API, or use it as a reference for your own UI Library.

## Getting started

  * [Importing the Library](https://biggaboy212.github.io/Cascade/Getting%20Started/importing-the-library/)
  * [Creating an Application](https://biggaboy212.github.io/Cascade/Getting%20Started/importing-the-library/)
  * [Example](https://biggaboy212.github.io/Cascade/Getting%20Started/example/)

## Supporting the project

If you'd like to support the project, consider donating via any of these:

  * **LTC**: `Lcd6pCPiVw9GH2MAaGkR8TX39EtL2LzFoa`
  * **SOL**: `EfwivXazedEw2kL2et92NKDkT3qrQ6pdxA8Bs7RKYRuN`
  * **ETH**: `0x85C09A302CBB42a321E737E6E4dDAC0Eebc4ac3E`
  * **BTC**: `bc1qlhym0f0lejwlhd47gext8g7fgej366j7z85vvx`

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/index.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/index.md "View source of this page")

# Base Component

`BaseComponent` is the base class from which all Cascade components inherit. It provides core fields such as `Type`, `Theme`, and `Structures`.

## Summary

### Properties

Property | Type | Description
---|---|---
`Type` | `string` | **[Read-only]** Defines the Component's class.
`Theme` | `string` | **[Read-only]** Inherited theme from the calling component.
`Structures` | `{[string]: Instance?}` | **[Read-only]** Table of defined component structures.
`__instance` | `Instance` | **[Read-only]** Returns the unwrapped roblox instance of the component. Useful for interacting with Roblox API's that won't accept our wrapped instance

Warning

Note that to access a component's actual instance, you will need to index `__instance` on it, this will return the actual roblox object instead of a merged metatable with the custom object.

## Types

    export type BaseComponent = {
        Type: string,
        Theme: Theme,
        Structures: { [string]: Instance | { any } },

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Getting Started/The API/index.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Getting Started/The API/index.md "View source of this page")

# The API

## Summary

### Properties

Property | Type | Description
---|---|---
`Themes` | `{ [string]: Theme }` | **[Read-only]** Provides default application theme modes . See [all themes](https://biggaboy212.github.io/Cascade/Getting%20Started/The%20API/themes/).
`Accents` | `{ [string]: Accent }` | **[Read-only]** Provides preset accents. See [all accents](https://biggaboy212.github.io/Cascade/Getting%20Started/The%20API/accents/).
`Symbols` | `{ [string]: string }` | **[Read-only]** A large list of symbols (exported from Apple SF Symbols). See [all symbols](https://sf-symbols-one.vercel.app/).

### Methods

Method | Arguments | Description | Returns
---|---|---|---
`New` | `properties: AppProperties` | Creates a new [App](https://biggaboy212.github.io/Cascade/Getting%20Started/The%20API/app/), this returns every component you can then call and will appear on screen. | [App](https://biggaboy212.github.io/Cascade/Getting%20Started/The%20API/app/)
`Component` | `properties: ComponentProperties?` | Creates a standalone [component context](https://biggaboy212.github.io/Cascade/Getting%20Started/The%20API/standalone/) without `App` `ScreenGui` overhead. | [ComponentContext](https://biggaboy212.github.io/Cascade/Getting%20Started/The%20API/standalone/)
`RegisterComponent` | `(name: string, maker: function)` | Register a custom component to the API. Reference [Custom Components](https://biggaboy212.github.io/Cascade/Custom%20Components/introduction/) | void

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Window.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Window.md "View source of this page")

# Window

`Window` is a high-level container that handles all base interaction with the user for you. Usually this is the first component called and most components stem from here.

## Summary

### Properties

Property | Type | Description
---|---|---
`Searching` | `boolean?` | Shows a text field in the title bar which can be used to search pages for content.
`Draggable` | `boolean?` | Enables window dragging via mouse or touch device.
`Resizable` | `boolean?` | Enables resizing via sides or corners.
`Title` | `string?` | Title displayed in the content titlebar.
`Subtitle` | `string?` | Subtitle displayed under the title. Will be visually disabled if not defined.
`Maximized` | `boolean?` | Should the window be maximized by default?
`Minimized` | `boolean?` | Should the window be minimized by default?
`Dropshadow` | `boolean?` | Enables a dropshadow effect on the window.
`UIBlur` | `boolean?` | Applies a background blur effect to the sidebar.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type WindowProperties = Frame & {
        Searching: boolean?,
        Draggable: boolean?,
        Resizable: boolean?,
        Title: string?,
        Subtitle: string?,
        Maximized: boolean?,
        Minimized: boolean?,

        Dropshadow: boolean?,
        UIBlur: boolean?,

    type Window = BaseComponent & Components & WindowProperties

### Function Signature

    function(self, properties: WindowProperties?): Window

## Example

    local window = app:Window({
        Title = "Cascade",
        Subtitle = "This is my subtitle.",
    })

    print(window:IsA("Frame")) --> true
    print(window.ClassName) --> "Frame"
    print(window.Type) --> "Window"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Getting Started/The API/themes.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Getting Started/The API/themes.md "View source of this page")

# Themes

Cascade provides presets for setting the overall application-wide theme.

## Summary

Theme
Light **(Default)**
Dark

## Example

    application.Theme = cascade.Themes.Dark

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Tab.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Tab.md "View source of this page")

# Tab

A `Tab` separates content into different pages, and lets users navigate between them using a button on the sidebar. Each tab has an associated [Page](https://biggaboy212.github.io/Cascade/Components/Page/) that displays when the tab is selected.

## Summary

### Properties

Property | Type | Description
---|---|---
`Title` | `string?` | The tab's display title.
`Icon` | `string?` | The `rbxassetid://` of the image to display. You can use cascade.Symbols for pre-made symbols.
`Indentation` | `number?` | The tab indentation level/how far right it is. This is automatically increased by `1` when you nest a tab on another tab.
`Selected` | `boolean?` | Whether or not the tab is selected by default. Defaults to false. Only one tab in a section should be selected.
`Page` | `Page?` | A custom page component to use for this tab. If not provided, a default page is automatically created. See [Page](https://biggaboy212.github.io/Cascade/Components/Page/) for more information.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

Method | Signature | Description
---|---|---
`Navigate` | `(self: Tab, page: Page) -> nil` | Switch the tab's displayed page to a different page. Useful for multi-step workflows or navigating between different views within a tab.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type TabProperties = Frame & {
        Title: string?,
        Icon: string?,
        Indentation: number?,
        Selected: boolean?,
        Page: Page?,

    type Tab = BaseComponent & Components & TabProperties & {
        Navigate: (self: Tab, page: Page) -> nil,

### Function Signature

    function(self, properties: TabProperties?): Tab

## Creating Tabs

    local tab = section:Tab({
        Title = "Settings",
        Icon = cascade.Symbols.gear,
    })

### Tab with Custom Page

Tabs automatically make pages for you, but you can pass a custom one.

    local customPage = app:Page()

    -- Add content to the page
    customPage:Form():Row():Right():Toggle({
        Value = false,
    })

    -- Use the page in a tab
    local tab = section:Tab({
        Title = "Settings",
        Page = customPage,
    })

## Navigating between Pages

The `Navigate` method allows you to switch pages within a tab. This is useful for multi-step workflows or progressive disclosure:

    local tab = section:Tab({
        Title = "Workflow",
        Selected = true,
    })

    local page1 = app:Page()
    local page2 = app:Page()

    -- Setup pages with navigation buttons
    do
        local form = page1:Form()
        form:Row():Right():Button({
            Label = "Next",
            Pushed = function()
                tab:Navigate(page2)
            end,
        })
    end

    do
        local form = page2:Form()
        form:Row():Right():Button({
            Label = "Back",
            State = "Secondary",
            Pushed = function()
                tab:Navigate(page1)
            end,
        })
    end

    -- Show page 1 initially
    tab:Navigate(page1)

## Nested Tabs

Tabs can be nested for hierarchical navigation. Indentation is automatically handled:

    local mainTab = section:Tab({
        Title = "Main",
        Icon = cascade.Symbols.squareStack3dUp,
    })

    -- Chain additional tabs to create indentation
    mainTab:Tab({
        Title = "Sub 1",
    })

    mainTab:Tab({
        Title = "Sub 2",
    })

## Tab Selection

Only one tab in a section can be selected at a time. Clicking a tab automatically deselects others:

    local tab1 = section:Tab({
        Title = "Tab 1",
        Selected = true,  -- This tab starts selected
    })

    local tab2 = section:Tab({
        Title = "Tab 2",
        Selected = false,  -- Defaults to false
    })

    -- When tab2 is clicked, tab1 is automatically deselected

Manually change the selected state:

    local tab1 = section:Tab({ Title = "Tab 1" })
    local tab2 = section:Tab({ Title = "Tab 2" })

    -- Later in your code
    tab2.Selected = true  -- Selects tab2
    -- tab1 is automatically deselected by the system

## Examples

    local tab = section:Tab({
        Selected = true,
        Title = "Tab",
        Icon = cascade.Symbols.squareStack3dUp,
    })

    print(tab:IsA("Frame")) --> true
    print(tab.ClassName) --> "Frame"
    print(tab.Type) --> "Tab"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/TextField.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/TextField.md "View source of this page")

# TextField

A `TextField` is a rectangular area in which people enter or edit small, specific pieces of text.

## Summary

### Properties

Property | Type | Description
---|---|---
`Placeholder` | `string?` | The text placeholder to instruct users on how to interact with the component.
`Value` | `Enum.Keycode?` | The text in the field.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Event | Signature | Description
---|---|---
`TextChanged` | `((self: TextField, text: string) -> unknown)?` | A Callback function that is triggered when the text field's text has been modified.
`ValueChanged` | `((self: TextField, value: string) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type TextFieldProperties = Frame & {
        Placeholder: string?,
        Value: string?,
        TextChanged: ((self: TextField, text: string) -> unknown)?,
        ValueChanged: ((self: TextField, value: string) -> unknown)?,

    type TextField = BaseComponent & Components & TextFieldProperties

### Function Signature

    function(self, properties: TextFieldProperties): TextField

## Example

    local textField = row:Right():TextField({
        Value = "Label",
        ValueChanged = function(self, value: string)
            print("Value changed:", value)
        end,
        TextChanged = function(self, value: string)
            print("Text changed:", value)
        end,
    })

    print(textField:IsA("Frame")) --> true
    print(textField.ClassName) --> "Frame"
    print(textField.Type) --> "TextField"

    textField.Value = "Hi" --> Value changed: "Hi"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/KeybindField.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/KeybindField.md "View source of this page")

# KeybindField

A `KeybindField` is a rectangular area in which people can enter and store `Enum.KeyCode` `InputObject`'s, then trigger a callback after pressing the stored KeyCode

## Summary

### Properties

Property | Type | Description
---|---|---
`Placeholder` | `string?` | The text placeholder to instruct users on how to interact with the component.
`Value` | `Enum.Keycode?` | The default shortcut the `KeybindField` is bound to.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Event | Signature | Description
---|---|---
`BindPressed` | `((self: KeybindField, value: Enum.KeyCode, inputComplete: boolean, gameProcessedEvent: boolean) -> unknown)?` | A Callback function that is triggered when the `Value` KeyCode has been pressed.
`ValueChanged` | `((self: KeybindField, value: Enum.KeyCode) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type KeybindFieldProperties = Frame & {
        Placeholder: string?,
        Value: Enum.KeyCode?,
        BindPressed: ((
            self: KeybindField,
            value: Enum.KeyCode,
            inputComplete: boolean,
            gameProcessedEvent: boolean
        ) -> unknown)?,
        ValueChanged: ((self: KeybindField, value: Enum.KeyCode) -> unknown)?,

    type KeybindField = BaseComponent & Components & KeybindFieldProperties

### Function Signature

    function(self, properties: KeybindFieldProperties): KeybindField

## Example

    local keybindField = row:Right():KeybindField({
        ValueChanged = function(self, value: Enum.KeyCode)
            print("Value changed:", value)
        end,
        BindPressed = function(
            self,
            value: Enum.KeyCode,
            inputComplete: boolean,
            gameProcessedEvent: boolean
        )
            if not inputComplete or gameProcessedEvent then
                return
            end

            print("Pressed bind:", value)
        end,
    })

    print(keybindField:IsA("Frame")) --> true
    print(keybindField.ClassName) --> "Frame"
    print(keybindField.Type) --> "KeybindField"

    keybindField.Value = Enum.KeyCode.Z --> Value changed: Enum.KeyCode.Z

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Symbol.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Symbol.md "View source of this page")

# Symbol

A `Symbol` is a static image component mainly used to add visual context and branding to an interface.

## Summary

### Properties

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-properties)

### Methods

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-methods)

### Events

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-events)

## Types

    type SymbolProperties = ImageLabel & {
        Style: ("Primary" | "Secondary")?,

    type Symbol = BaseComponent & Components & SymbolProperties

### Function Signature

    function(self, properties: SymbolProperties): Symbol

## Example

    local symbol = row:Right():Symbol({
        Image = cascade.Symbols.sunMin,
    })

    print(symbol:IsA("ImageLabel")) --> true
    print(symbol.ClassName) --> "ImageLabel"
    print(symbol.Type) --> "Symbol"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Slider.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Slider.md "View source of this page")

# Slider

A `Slider` is a horizontal track with a control, called a thumb, that people can adjust between a minimum and maximum value.

## Summary

### Properties

Property | Type | Description
---|---|---
`Minimum` | `number?` | The minimum value the slider can go.
`Maximum` | `number?` | The maximum value the slider can reach.
`Value` | `number?` | The slider's current value.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-properties)

### Methods

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-methods)

### Events

Event | Signature | Description
---|---|---
`ValueChanged` | `((self: Slider, value: number) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-events)

## Types

    type SliderProperties = ImageLabel & {
        Minimum: number?,
        Maximum: number?,
        Value: number?,
        ValueChanged: ((self: Slider, value: number) -> unknown)?,

    type Slider = BaseComponent & Components & SliderProperties

### Function Signature

    function(self, properties: SliderProperties): Slider

## Example

    local slider = row:Right():Slider({
        Minimum = 0,
        Maximum = 10
        Value = 5,
        ValueChanged = function(self, value: number)
            print("Value changed:", value)
        end,
    })

    print(slider:IsA("ImageLabel")) --> true
    print(slider.ClassName) --> "ImageLabel"
    print(slider.Type) --> "Slider"

    slider.Value += 1 --> Value changed: 6

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Form.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Form.md "View source of this page")

# Form

A `Form` is a vertical container that organizes interface elements into clearly separated rows, using consistent styling like dividers to emphasize structure.

## Summary

### Properties

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type FormProperties = Frame

    type Form = BaseComponent & Components & FormProperties

### Function Signature

    function(self, properties: FormProperties?): Form

## Example

    local form = section:Form()

    print(form:IsA("Frame")) --> true
    print(form.ClassName) --> "Frame"
    print(form.Type) --> "Form"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/PopUpButton.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/PopUpButton.md "View source of this page")

# PopUpButton

A `PopUpbutton` displays a menu of mutually exclusive options.

## Summary

### Properties

Property | Type | Description
---|---|---
`Options` | `{[number]: string}?` | You can use this table to pre-define options. Note that doing it this way will not give you access to the option instances themselves.
`Expanded` | `boolean?` | Defines the state of the dropdown disclosure.
`Maximum` | `number?` | Maximum number of selectable options. Defaults to `1` (single-select).
`Value` | `number? or {number}?` | The selected index (single) or a table of selected indices when `Maximum > 1`.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

Method | Signature | Description
---|---|---
`Option` | `(Name: string?) -> Frame` | Can be used to seperately create options, use this if you want to access the option instances themselves. An example of use would be a dynamically updating playerlist.
`Remove` | `(Index: number?) -> nil,` | Can be used to remove options from the pop-up menu, this automatically removes it from the options list as well.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Event | Signature | Description
---|---|---
`ValueChanged` | `((self: PopUpButton, value: number or {number}) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type PopUpButtonProperties = Frame & {
        Options: { [number]: string }?,
        Expanded: boolean?,
        Maximum: number?,
        Value: (number | { number })?,
        ValueChanged: ((self: PopUpButton, value: number | { number }) -> unknown)?,

    type PopUpButton = BaseComponent & Components & PopUpButtonProperties & {
        Option: (Name: string?) -> Frame,
        Remove: (Index: number?) -> nil,

### Function Signature

    function(self, properties: PopUpButtonProperties): PopUpButton

## Example

    local popUpButton = row:Right():PopUpButton({
        Options = {
            "Item One",
            "Item Two",
        },
        ValueChanged = function(self, value: number)
            print("Value changed:", self.Options[value])
        end,
    })

    print(popUpButton:IsA("Frame")) --> true
    print(popUpButton.ClassName) --> "Frame"
    print(popUpButton.Type) --> "PopUpButton"

    popUpButton.Value = 3 --> Value changed: "Item Three"

    local itemThree = popUpButton:Option("Item Three")

    print(itemThree.ClassName) --> Frame
    popUpButton:Remove(13)

## Multi-select Example

    local multi = row:Right():PopUpButton({
        Options = {"One","Two","Three","Four"},
        Maximum = 3,
        ValueChanged = function(self, value)
            -- `value` is ALWAYS a table of indices when `Maximum > 1` even if only 1 value is selected.
            print("Selections:")
            for _, idx in ipairs(value or {}) do
                print(self.Options[idx])
            end
        end,
    })

    local five = multi:Option("Five")

    multi.Value = {1, 3}

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/PageSection.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/PageSection.md "View source of this page")

# PageSection

A `PageSection` separates content in a page into digestible sections via a Title stack. For example, a settings page might have:

  * Appearance
  * Input
  * Effects

## Summary

### Properties

Property | Type | Description
---|---|---
`Title` | `string?` | The section's title.
`Subtitle` | `string?` | The section's sub-title. If left nil, the subtitle will be non-visible.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type PageSectionProperties = Frame & {
        Title: string?,
        Subtitle: string?,

    type PageSection = BaseComponent & Components & PageSectionProperties

### Function Signature

    function(self, properties: PageSectionProperties?): PageSection

## Example

    local pageSection = tab:PageSection({
        Title = "Effects",
        Subtitle = "These effects may be resource intensive across different systems.",
    })

    print(pageSection:IsA("Frame")) --> true
    print(pageSection.ClassName) --> "Frame"
    print(pageSection.Type) --> "PageSection"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Getting Started/The API/accents.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Getting Started/The API/accents.md "View source of this page")

# Accents

Cascade provides presets for setting the overall application-wide accent.

## Summary

Accent
Blue **(Default)**
Red
Orange
Yellow
Green
Graphite
Pink
Purple

## Example

    application.Accent = cascade.Accents.Red

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/HStack.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/HStack.md "View source of this page")

# HStack

A `HStack` is a frame that horizontally sorts content.

## Summary

### Properties

Property | Type | Description
---|---|---
`Padding` | `UDim?` | The padding between objects.
`HorizontalAlignment` | `Enum.HorizontalAlignment?` | The content X axis alignment.
`VerticalAlignment` | `Enum.VerticalAlignment?` | The content Y axis alignment.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type StackProperties = Frame & {
        Padding: UDim?,
        HorizontalAlignment: Enum.HorizontalAlignment?,
        VerticalAlignment: Enum.VerticalAlignment?,

    type Stack = BaseComponent & Components & StackProperties

### Function Signature

    function(self, properties: StackProperties?): HStack

## Example

    local hStack = row:Right():HStack()

    print(hStack:IsA("Frame")) --> true
    print(hStack.ClassName) --> "Frame"
    print(hStack.Type) --> "HStack"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/PullDownButton.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/PullDownButton.md "View source of this page")

# PullDownButton

A `PullDownButton` displays a menu of mutually exclusive options.

## Summary

### Properties

Property | Type | Description
---|---|---
`Options` | `{[number]: string}?` | You can use this table to pre-define options. Note that doing it this way will not give you access to the option instances themselves.
`Expanded` | `boolean?` | Defines the state of the dropdown disclosure.
`Label` | `string?` | Shows a label next to the disclosure button. Use it to describe the menu's content.
`Value` | `number?` | The numeric index of the option to be selected.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

Method | Signature | Description
---|---|---
`Option` | `(Name: string?) -> Frame` | Can be used to seperately create options, use this if you want to access the option instances themselves.
`Remove` | `(Index: number?) -> nil,` | Can be used to remove options from the pull-down menu, this automatically removes it from the options list as well.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Event | Signature | Description
---|---|---
`ValueChanged` | `((self: PullDownButton, value: string) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type PullDownButtonProperties = Frame & {
        Options: { [number]: string }?,
        Expanded: boolean?,
        Label: string?,
        Value: number?,
        ValueChanged: ((self: PullDownButton, value: number) -> unknown)?,

    type PullDownButton = BaseComponent & Components & PullDownButtonProperties & {
        Option: (Name: string?) -> Frame,
        Remove: (Index: number?) -> nil,

### Function Signature

    function(self, properties: PullDownButtonProperties): PullDownButton

## Example

    local pullDownButton = row:Right():PullDownButton({
        Label = "Action",
        Options = {
            "Action One",
            "Action Two",
        },
        ValueChanged = function(self, value: number)
            print("Action selected:", self.Options[value])
        end,
    })

    print(pullDownButton:IsA("Frame")) --> true
    print(pullDownButton.ClassName) --> "Frame"
    print(pullDownButton.Type) --> "PullDownButton"

    pullDownButton.Value = 3 --> Value changed: "Item Three"

    local itemThree = pullDownButton:Option("Item Three")

    print(itemThree.ClassName) --> Frame
    pullDownButton:Remove(13)

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Toggle.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Toggle.md "View source of this page")

# Toggle

A `Toggle` lets people choose between a pair of opposing states, like on and off, using a different appearance to indicate each state.

## Summary

### Properties

Property | Type | Description
---|---|---
`Value` | `boolean?` | The toggle's state. `false` for off, `true` for on

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Event | Signature | Description
---|---|---
`ValueChanged` | `((self: Toggle, value: boolean) -> unknown)?` | A callback function that is triggered when the `Value` property has been modified.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type ToggleProperties = Frame & {
        Value: boolean?,
        ValueChanged: ((self: Toggle, value: boolean) -> unknown)?,

    type Toggle = BaseComponent & Components & ToggleProperties

### Function Signature

    function(self, properties: ToggleProperties): Toggle

## Example

    local toggle = row:Right():Toggle({
        Value = true,
        ValueChanged = function(self, value: boolean)
            print("Value changed:", value)
        end,
    })

    print(toggle:IsA("Frame")) --> true
    print(toggle.ClassName) --> "Frame"
    print(toggle.Type) --> "Toggle"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Button.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Button.md "View source of this page")

# Button

A `Button` initiates an instantaneous action.

## Summary

### Properties

Property | Type | Description
---|---|---
`State` | `("Primary" or "Secondary" or "Destructive")?` | Determines the weight of the button. Suggests to the user its impact on surrounding content.
`Label` | `string?` | The text content of the button.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `TextButton`](https://create.roblox.com/docs/reference/engine/classes/TextButton#summary-properties)

### Methods

[View all inherited from `TextButton`](https://create.roblox.com/docs/reference/engine/classes/TextButton#summary-methods)

### Events

Event | Signature | Description
---|---|---
`Pushed` | `((self: Button) -> unknown)?` | A callback function that is triggered when the button has been fully clicked or tapped.

[View all inherited from `TextButton`](https://create.roblox.com/docs/reference/engine/classes/TextButton#summary-events)

## Types

    type ButtonProperties = TextButton & {
        State: ("Primary" | "Secondary" | "Destructive")?,
        Label: string?,
        Pushed: ((self: Button) -> unknown)?,

    type Button = BaseComponent & Components & ButtonProperties

### Function Signature

    function(self, properties: ButtonProperties): Button

## Example

    local button = row:Right():Button({
        Label = "Button",
        State = "Primary",
        Pushed = function(self)
            print("Pushed")
        end,
    })

    print(button:IsA("TextButton")) --> true
    print(button.ClassName) --> "TextButton"
    print(button.Type) --> "Button"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/RadioButtonGroup.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/RadioButtonGroup.md "View source of this page")

# RadioButtonGroup

A `RadioButtonGroup` lets people choose an option from a set of mutually exclusive choices, using a circular indicator to show the selected option.

## Summary

### Properties

Property | Type | Description
---|---|---
`Options` | `{[number]: string}?` | You can use this table to pre-define options. Note that doing it this way will not give you access to the option instances themselves.
`Value` | `number?` | The numeric index of the option to be selected.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

Method | Signature | Description
---|---|---
`Option` | `(Name: string?) -> Frame` | Can be used to seperately create options, use this if you want to access the option instances themselves. An example of use would be a dynamically updating playerlist.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Event | Signature | Description
---|---|---
`ValueChanged` | `((self: RadioButtonGroup, value: string) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type RadioButtonGroupProperties = Frame & {
        Options: { [number]: string }?,
        Value: number?,
        ValueChanged: ((self: RadioButtonGroup, value: number) -> unknown)?,

    type RadioButtonGroup = BaseComponent & Components & RadioButtonGroupProperties & {
        Option: (Name: string?) -> Frame,

### Function Signature

    function(self, properties: RadioButtonGroupProperties): RadioButtonGroup

## Example

    local radioButtonGroup = row:Right():RadioButtonGroup({
        Options = {
            "Option 1",
            "Option 2",
        },
        ValueChanged = function(self, value: number)
            print("Value changed:", self.Options[value])
        end,
    })

    print(radioButtonGroup:IsA("Frame")) --> true
    print(radioButtonGroup.ClassName) --> "Frame"
    print(radioButtonGroup.Type) --> "RadioButtonGroup"

    radioButtonGroup.Value = 2 --> Value changed: "Option 2"

    local option3 = radioButtonGroup:Option("Option 3")

    print(option3.ClassName) --> Frame
    radioButtonGroup.Value = 3 --> Value changed: "Option 3"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Notification.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Notification.md "View source of this page")

# Notification

A `Notification` gives people timely, high-value information they can understand at a glance.

## Summary

### Properties

Name | Type | Description
---|---|---
`Title` | `string` | The primary headline of the notification.
`Subtitle` | `string` | The secondary text providing more context.
`App` | `string?` | Optional text to display what feature or app triggered the notification.
`AppIcon` | `string?` | Optional image asset ID to show an icon in the top left.
`Icon` | `string?` | Optional image asset ID to show an icon in the top left, next to the title.
`Duration` | `number?` | How long (in seconds) the notification remains before auto-closing. Defaults to `6`. Use `0` for manual close only.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

Name | Returns | Description
---|---|---
`Close` | `nil` | Manually dismisses the notification.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

Name | Parameters | Description
---|---|---
`Closed` | `(self: Notification, fromUser: boolean)` | Fired when the notification is closed either via timeout or by the user.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type NotificationProperties = Frame & {
        Title: string,
        Subtitle: string,
        App: string?,
        AppIcon: string?,
        Icon: string?,
        Duration: number?,
        Closed: ((self: Notification, fromUserInput: boolean?) -> unknown)?,

    type Notification = BaseComponent & Components & NotificationProperties & {
        Close: (self: Notification) -> nil,

### Function Signature

    function(self, properties: NotificationProperties): Notification

## Example

    local notification = app:Notification({
        App = "CHAT",
        AppIcon = "rbxassetid://132228700346004",

        Title = "New Message",
        Subtitle = "You received a new message from a friend.",
        Icon = cascade.Symbols.bell, -- Icon appears beside the title label.

        Duration = 5,

        Closed = function(self, fromUser)
            print("Notification was dismissed! (from user: " .. tostring(fromUser) .. ")")
        end
    })

    -- Sometime later, if you need to manually close it:
    -- notification:Close()

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Stepper.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Stepper.md "View source of this page")

# Stepper

A `Stepper` is a two-segment control that people use to increase or decrease an incremental value.

## Summary

### Properties

Property | Type | Description
---|---|---
`Minimum` | `number?` | The minimum value the stepper can drop.
`Maximum` | `number?` | The maximum value the stepper can reach.
`Step` | `number?` | The increment to increase by on increase/decrease.
`Fielded` | `boolean?` | Whether the stepper should be attached to a editable field. Defaults to false.
`Value` | `number?` | The stepper's current value.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-properties)

### Methods

Method | Signature | Description
---|---|---
`Increment` | `() -> nil` | This increments the `Value` of the stepper by the given step number.
`Decrement` | `() -> nil` | This decrements the `Value` of the stepper by the given step number.

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-methods)

### Events

Event | Signature | Description
---|---|---
`ValueChanged` | `((self: Stepper, value: number) -> unknown)?` | A Callback function that is triggered when the `Value` property has been modified.

[View all inherited from `ImageLabel`](https://create.roblox.com/docs/reference/engine/classes/ImageLabel#summary-events)

## Types

    type StepperProperties = ImageLabel & {
        Minimum: number?,
        Maximum: number?,
        Step: number?,
        Fielded: boolean?,
        Value: number?,
        ValueChanged: ((self: Stepper, value: number) -> unknown)?,

    type Stepper = BaseComponent & Components & StepperProperties & {
        Increment: () -> nil,
        Decrement: () -> nil,

### Function Signature

    function(self, properties: StepperProperties): Stepper

## Example

    local stepper = row:Right():Stepper({
        Minimum = 0,
        Maximum = 5,
        Step = 0.1,
        Fielded = true,
        Value = 3,
        ValueChanged = function(self, value: number)
            print("Value changed:", value)
        end,
    })

    print(stepper:IsA("ImageLabel")) --> true
    print(stepper.ClassName) --> "ImageLabel"
    print(stepper.Type) --> "Stepper"

    stepper:Increment() --> Value changed: 3.01
    stepper.Value += 1 --> Value changed: 4.01

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Getting Started/interface-guidelines.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Getting Started/interface-guidelines.md "View source of this page")

# Interface Guidelines

For more information about Cascade’s Human Interface Guidelines, refer to [Apple’s Official HIG documentation](https://developer.apple.com/design/human-interface-guidelines/), which Cascade is based on.

## Why is this important?

Following established interface guidelines is essential to building high-quality user interfaces. While visually appealing components are important, it's equally critical to **understand how and when to use them.**

Cascade adopts Apple's Human Interface Guidelines to promote consistency, clarity, and intentional design. _By understanding these principles_ , you can create polished interfaces that are consistent with a user's everyday interface navigation experience. Knowing how to use components correctly leads to better user experiences and more maintainable UI systems.

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Row.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Row.md "View source of this page")

# Row

A `Row` is a horizontal container that splits its contents into `Left` and `Right` sections, providing a clear visual distinction between primary and secondary elements.

## Summary

### Properties

Property | Type | Description
---|---|---
`SearchIndex` | `string?` | A string that determines what the user has to type in to show this row in their search query

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

Method | Signature | Description
---|---|---
`Left` | `() -> Row` | Returns a shallow clone of `Row` with the container set to the row's **left** container.
`Right` | `() -> Row` | Returns a shallow clone of `Row` with the container set to the row's **right** container.

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type RowProperties = Frame & {
        SearchIndex: string?,

    type Row = BaseComponent & Components & RowProperties & {
        Left: (self: Row) -> Row,
        Right: (self: Row) -> Row,

### Function Signature

    function(self, properties: RowProperties?): Row

## Example

    local row = section:Row({
        SearchIndex = "Cool Row"
    })

    print(row:IsA("Frame")) --> true
    print(row.ClassName) --> "Frame"
    print(row.Type) --> "Row"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Custom Components/creating-a-component.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Custom Components/creating-a-component.md "View source of this page")

# Creating a Component

Building a custom component in Cascade involves understanding the component lifecycle and the context it lives in.

## The Component Maker

Every component is defined by a "maker" function. This function receives two arguments: `self` and `properties`.

### `self`

`self` is the chain context. eg

    cascade.RegisterComponent("Test", function(self)
        print(self.Type)
    end)

    ctx:Toggle():Test() -- "Toggle"

### `properties`

`properties` is the table passed by the user when calling your component. You should use this to customize the appearance and behavior of your component.

## Example: A "Card" Component

You can build a component using Cascade's internal modules or just return a raw Roblox instance.

    local creator = cascade.Creator
    local create = creator.Create

    cascade.RegisterComponent("Card", function(self, properties)
        -- Create the instance using cascade.Creator
        -- self.Theme is automatically available
        local frame = create("Frame")({
            Name = properties.Name or "Card",
            Size = properties.Size or UDim2.fromOffset(200, 100),
            Parent = properties.Parent or self.__container,

            -- Reactive State: You can use cascades built in __contextKeys and __dynamicKeys tables for reactive components. View existing cascade components for the best examples on this.

            create("UICorner")({
                CornerRadius = UDim.new(0, 8),
            }),
        })

        self:Label({
            Text = properties.Title or "Card Title",
            Parent = frame.__instance,
        })

        return frame
    end)

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/VStack.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/VStack.md "View source of this page")

# VStack

A `VStack` is a frame that vertically sorts content.

## Summary

### Properties

Property | Type | Description
---|---|---
`Padding` | `UDim?` | The padding between objects.
`HorizontalAlignment` | `Enum.HorizontalAlignment?` | The content X axis alignment.
`VerticalAlignment` | `Enum.VerticalAlignment?` | The content Y axis alignment.

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type StackProperties = Frame & {
        Padding: UDim?,
        HorizontalAlignment: Enum.HorizontalAlignment?,
        VerticalAlignment: Enum.VerticalAlignment?,

    type Stack = BaseComponent & Components & StackProperties

### Function Signature

    function(self, properties: StackProperties?): VStack

## Example

    local vStack = row:Right():VStack()

    print(vStack:IsA("Frame")) --> true
    print(vStack.ClassName) --> "Frame"
    print(vStack.Type) --> "VStack"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Custom Components/introduction.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Custom Components/introduction.md "View source of this page")

# Introduction

Cascade is designed to be extensible. You can register your own components using `cascade.RegisterComponent`, making them available on all `App` and `ComponentContext` objects.

## RegisterComponent

To register a component, you provide a name and a "maker" function.

### Function Signature

    function(name: string, make: (self: any, properties: any) -> (any, Instance?))

### Basic Example

    cascade.RegisterComponent("RedLabel", function(self, properties)
        local object = self:Label({
            Text = properties.Text or "Default Text",
            TextColor3 = Color3.fromRGB(255, 0, 0),
        })

        return object
    end)

    -- Now you can use it like any built-in component:
    local app = cascade.New()
    app:RedLabel({ Text = "Hello World" })

## Why use Custom Components?

  1. **Reusability**: Wrap complex UI patterns (like a titled row with a slider and a value label) into a single component.
  2. **Consistency**: Ensure consistent styling and behavior across your application by creating higher-level primitives.
  3. **Theming**: Custom components automatically inherit the theme and accent from their parent context.

## Next Steps

  * [Creating a Component](https://biggaboy212.github.io/Cascade/Custom%20Components/creating-a-component/): Learn how to build a component from scratch.
  * [Internal Modules](https://biggaboy212.github.io/Cascade/Custom%20Components/internal-modules/): Discover how to use `Creator` and `Binder` to build powerful components.

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Page.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Page.md "View source of this page")

# Page

A `Page` is a container that holds content in a scrollable area. Pages are typically used with [Tabs](https://biggaboy212.github.io/Cascade/Components/Tab/) to create navigable multi-page interfaces, but can also be used standalone.

## Summary

### Properties

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-properties)

### Methods

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-methods)

### Events

[View all inherited from `Frame`](https://create.roblox.com/docs/reference/engine/classes/Frame#summary-events)

## Types

    type PageProperties = Frame

    type Page = BaseComponent & Components & PageProperties

### Function Signature

    function(self, properties: PageProperties?): Page

## Pages and Tabs

Pages are most commonly used with [Tabs](https://biggaboy212.github.io/Cascade/Components/Tab/). Each Tab has an associated Page that displays when the tab is selected.

When you create a tab, it automatically creates a page for you:

### Tabs with custom pages

You can also pass your own page to a tab:

    local customPage = app:Page()

    -- Add content to your page
    customPage:Form():PageSection({ Title = "Settings" })

    -- Pass it to the tab
    local tab = section:Tab({
        Title = "Settings",
        Page = customPage,
    })

### Page Navigation

Use the `Navigate` method on a tab to switch between pages programmatically:

    local homePage = app:Page()
    local settingsPage = app:Page()

    local tab = section:Tab({
        Title = "Routing",
        Selected = false,
    })

    -- Setup home page
    do
        local form = homePage:Form()
        form:Row():Right():Button({
            Label = "Go to Settings",
            Pushed = function()
                tab:Navigate(settingsPage)  -- Switch to settings page
            end,
        })
    end

    -- Setup settings page
    do
        local form = settingsPage:Form()
        form:Row():Right():Button({
            Label = "Back to Home",
            Pushed = function()
                tab:Navigate(homePage)  -- Switch back to home page
            end,
        })
    end

    -- Show home page initially
    tab:Navigate(homePage)

## Examples

    local app = cascade.New({
        Theme = cascade.Themes.Light,
    })

    local window = app:Window({
        Title = "My App",
    })

    local section = window:Section({
        Disclosure = false,
    })

    local tab = section:Tab({
        Title = "Navigation",
        Icon = cascade.Symbols.squareStack3dUp,
        Selected = false,
    })

    -- Create pages
    local page1 = app:Page()
    local page2 = app:Page()

    -- Setup page 1
    do
        local form = page1:Form()

        form:Row():Left():TitleStack({
            Title = "Home Page",
            Subtitle = "Welcome to the app",
        })

        form:Row():Right():Button({
            Label = "Go to Next Page",
            Pushed = function()
                tab:Navigate(page2)
            end,
        })
    end

    -- Setup page 2
    do
        local form = page2:Form()

        form:Row():Left():TitleStack({
            Title = "Second Page",
            Subtitle = "You've navigated here",
        })

        form:Row():Right():Button({
            Label = "Back to Home",
            State = "Secondary",
            Pushed = function()
                tab:Navigate(page1)
            end,
        })
    end

    -- Show page 1 initially
    tab:Navigate(page1)

    local page = app:Page()

    print(page:IsA("Frame")) --> true
    print(page.ClassName) --> "ScrollingFrame"
    print(page.Type) --> "Page"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Components/Label.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Components/Label.md "View source of this page")

# Label

A `Label` is a static piece of text that people can read, but not edit.

## Summary

### Properties

[View all inherited from `BaseComponent`](https://biggaboy212.github.io/Cascade/Components/#properties)

[View all inherited from `TextLabel`](https://create.roblox.com/docs/reference/engine/classes/TextLabel#summary-properties)

### Methods

[View all inherited from `TextLabel`](https://create.roblox.com/docs/reference/engine/classes/TextLabel#summary-methods)

### Events

[View all inherited from `TextLabel`](https://create.roblox.com/docs/reference/engine/classes/TextLabel#summary-events)

## Types

    type LabelProperties = TextLabel

    type Label = BaseComponent & Components & LabelProperties

### Function Signature

    function(self, properties: LabelProperties): Label

## Example

    local label = row:Right():Label({
        Text = "Label"
    })

    print(label:IsA("TextLabel")) --> true
    print(label.ClassName) --> "TextLabel"
    print(label.Type) --> "Label"

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Getting Started/example.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Getting Started/example.md "View source of this page")

# Example

View a example application that can be made with Cascade.

Note

You can view a larger test application here which has full keybind minimization and mobile resize already added: [tests/test.luau](https://github.com/biggaboy212/Cascade/blob/main/tests/test.luau)

## Creating a basic app

### Code

    -- (1)

    -- Create our main application.
    local app = cascade.New({
        Theme = cascade.Themes.Light,
        Accent = cascade.Accents.Blue, -- Optional, defaults to Blue
    })

    do -- Make the main window
        local window = app:Window({
            Title = "My Window",
            Subtitle = "My window",
        })

        do -- Make a static tab section
            local section = window:Section({ Title = "Section title" })

            do -- Make our main tab
                local tab = section:Tab({
                    Selected = true,
                    Title = "Main",
                    Icon = cascade.Symbols.squareStack3dUp,
                })

                do
                    local form = tab:Form()

                    do -- Make the toggle
                        local row = form:Row()

                        -- You can of course add a wrapper to simplify creating page components if you only want a title stack and a component to go along.
                        row:Left():TitleStack({
                            Title = "Toggle",
                            Subtitle = "My toggle",
                        })

                        row:Right():Toggle()
                    end

                    do -- Make the button
                        local row = form:Row()

                        row:Left():TitleStack({
                            Title = "Button",
                            Subtitle = "My button",
                        })

                        row:Right():Button({ Label = "Click me" })
                    end
                end
            end
        end
    end

  1. This assumes you already have cascade imported and defined as `cascade` above this.

### Result

## Adding a settings page

### Code

Add this below the existing tab.

    do -- Window
        local tab = section:Tab({
            Title = "Window",
            Icon = cascade.Symbols.sidebarLeft,
        })

        do -- Appearance
            local form = tab:PageSection({ Title = "Appearance" }):Form() -- (1)

            do -- Dark mode
                local row = form:Row({
                    SearchIndex = "Dark mode",
                })

                row:Left():TitleStack({
                    Title = "Dark mode",
                    Subtitle = "Dark Mode is a application appearance setting that uses a dark color palette to provide a comfortable viewing experience tailored for low-light environments.",
                })

                row:Right():Toggle({
                    Value = app.Theme == cascade.Themes.Dark,
                    ValueChanged = function(self, value: boolean)
                        app.Theme = value and cascade.Themes.Dark or cascade.Themes.Light
                    end,
                })
            end
        end

        do -- Input
            local form = tab:PageSection({ Title = "Input" }):Form()

            do -- Searching
                local row = form:Row({
                    SearchIndex = "Searchable",
                })

                row:Left():TitleStack({
                    Title = "Searchable",
                    Subtitle = "Allows pages to be searched using a text field in the title bar.",
                })

                row:Right():Toggle({
                    Value = window.Searching,
                    ValueChanged = function(self, value: boolean)
                        window.Searching = value
                    end,
                })
            end

            do -- Draggable
                local row = form:Row({
                    SearchIndex = "Draggable",
                })

                row:Left():TitleStack({
                    Title = "Draggable",
                    Subtitle = "Allows users to move the window with a mouse or touch device.",
                })

                row:Right():Toggle({
                    Value = window.Draggable,
                    ValueChanged = function(self, value: boolean)
                        window.Draggable = value
                    end,
                })
            end

            do -- Resizable
                local row = form:Row({
                    SearchIndex = "Resizable",
                })

                row:Left():TitleStack({
                    Title = "Resizable",
                    Subtitle = "Allows users to resize the window with a mouse or touch device.",
                })

                row:Right():Toggle({
                    Value = window.Resizable,
                    ValueChanged = function(self, value: boolean)
                        window.Resizable = value
                    end,
                })
            end
        end

        do -- Effects
            local form = tab:PageSection({
                Title = "Effects",
                Subtitle = "These effects may be resource intensive across different systems.",
            }):Form()

            do -- Dropshadow
                local row = form:Row({
                    SearchIndex = "Dropshadow",
                })

                row:Left():TitleStack({
                    Title = "Dropshadow",
                    Subtitle = "Enables a dropshadow effect on the window.",
                })

                row:Right():Toggle({
                    Value = window.Dropshadow,
                    ValueChanged = function(self, value: boolean)
                        window.Dropshadow = value
                    end,
                })
            end

            do -- UI Blur
                local row = form:Row({
                    SearchIndex = "Background blur",
                })

                row:Left():TitleStack({
                    Title = "Background blur",
                    Subtitle = "Enables a UI background blur effect on the window. This can be detectable in some games.",
                })

                row:Right():Toggle({
                    Value = window.UIBlur,
                    ValueChanged = function(self, value: boolean)
                        window.UIBlur = value
                    end,
                })
            end
        end
    end

  1. You can create a page section to organize forms, which helps the reader quickly scan over content in a tab to see what they need to do.

### Result

Back to top


[ ](https://github.com/biggaboy212/Cascade/edit/main/docs/Custom Components/internal-modules.md "Edit this page") [ ](https://github.com/biggaboy212/Cascade/raw/main/docs/Custom Components/internal-modules.md "View source of this page")

# Internal Modules

Cascade exposes its core internal modules via the `cascade` object. These modules are essential for building high-quality, reactive components.

## `cascade.Creator`

The `Creator` module is responsible for creating and styling Roblox instances.

### `Creator.Create(className: string)`

Returns a function that allows you to create an instance and apply properties. It also supports nesting of children.

    local create = cascade.Creator.Create

    local frame = create("Frame")({
        Name = "MyFrame",
        Size = UDim2.fromScale(1, 1),

        create("UICorner")({ CornerRadius = UDim.new(0, 4) }),
    })

### `Creator.Value(initial: any)`

Creates a reactive value (a "State"). When the value of this state changes, any instance properties bound to it will automatically update.

## `cascade.Binder`

The `Binder` module provides the glue between your custom component objects and the underlying Roblox instances.

### `Binder.Wrap(object, bindings, instance?, excludes?)`

Creates a proxy that merges your object's custom logic with the Roblox instance's properties.

  * **`object`**: Your custom table containing component state and methods.
  * **`bindings`**: A table of functions to call when specific keys are set on the proxy.
  * **`instance`**: The raw Roblox instance to proxy properties to.
  * **`excludes`**: A list of property names that should not be automatically applied to the instance.

### `Binder.Apply(properties, object, excludes?)`

Sets a table of properties onto an object, ignoring any keys in `excludes`. This is typically used to apply user-provided properties during component initialization.

## `cascade.Symbols`

A lookup table containing hundreds of icons.

    local icon = cascade.Symbols.squareStack3dUp

## Hidden Properties

When your component is called as a child of another, it gains access to specific internal properties via `self`:

  * **`self.__container`**: The instance that children should be parented to. For example, `Window` sets this to its body area. This is why you should always fallback to `self.__container` for parenting.

Back to top