{"id":"biotoxin495/modalkit","name":"modalkit","scope":"biotoxin495","platform":"roblox","description":"A standalone, dynamically generated modal and confirmation prompt system for Roblox.","version":"1.0.1","latest":"1.0.1","versions":["1.0.0","1.0.1"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"7a27fcf91f9a06a6fd2e15500ba344abc8b58c2abc5e0b4cd108c9d00ffac6f4","likes":0,"downloads":0,"install":"forest install biotoxin495/modalkit","url":"https://forest.dev/p/roblox/biotoxin495/modalkit","files":"https://api.forest.dev/ai/package/roblox/biotoxin495/modalkit/files","readme":"# ModalKit — Runtime-generated modal prompts for Roblox\n\n**ModalKit**, a lightweight, standalone utility for displaying confirmations, alerts, and custom action prompts in Roblox.\n\nModalKit builds its interface at runtime, so it does not require a prebuilt GUI hierarchy, framework, or third-party runtime dependency. One action-based prompt system powers confirmations, alerts, destructive warnings, multi-choice dialogs, loading states, and custom modal content.\n\n## Quick example\n\nModalKit creates a manager and opens a prompt through a small client-side API.\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal ModalKit = require(ReplicatedStorage.ModalKit)\n\nlocal prompts = ModalKit.new()\n\nlocal confirmed, result = prompts:ConfirmAsync({\n    Title = \"Delete item?\",\n    Message = \"This action cannot be undone.\",\n    ConfirmText = \"Delete\",\n    Destructive = true,\n})\n\nif confirmed then\n    print(\"Delete the item\")\nelse\n    print(\"Cancelled or dismissed:\", result.Reason)\nend\n```\n\n## 🚀 Features\n\n### Runtime-generated UI\n\nModalKit creates its own `ScreenGui` and prompt hierarchy with `Instance.new`. No prebuilt GUI structure is required.\n\n### Action-based prompts\n\nUse `Confirm` and `Alert` for common cases, or `Open` for arbitrary actions with custom IDs, styles, callbacks, and result flags.\n\n### Flexible prompt lifecycle\n\nEvery prompt returns a handle with `Await`, `Update`, `Respond`, `Close`, and `Destroy` methods, plus completion and action signals.\n\n### Queueing and overlap policies\n\nPrompts can be queued, replace the active prompt, or be rejected while another prompt is open. Policies can be selected globally or per prompt.\n\n### Input and dismissal support\n\nButtons use `Activated`, covering mouse, touch, and gamepad input. Enter activates the default action, while Escape, gamepad B, the close button, and the backdrop can dismiss a prompt when enabled.\n\n### Themes and custom content\n\nUse global or per-prompt theme overrides, built-in action styles, custom named styles, and runtime-generated content builders with cleanup support.\n\n### Fully typed, no dependencies\n\nThe module uses strict Luau and has no external runtime dependencies or framework requirements.\n\n## 📖 Basic usage\n\nPlace the `ModalKit` ModuleScript somewhere accessible to client scripts, such as `ReplicatedStorage`. ModalKit must be required and used from a `LocalScript` or another client ModuleScript.\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal ModalKit = require(ReplicatedStorage.ModalKit)\nlocal prompts = ModalKit.new()\n```\n\n### Example: confirmation prompt\n\n`Confirm` creates a standard cancel/confirm dialog and returns a `PromptHandle`.\n\n```lua\nlocal handle = prompts:Confirm({\n    Title = \"Leave the match?\",\n    Message = \"Your current progress will be lost.\",\n    ConfirmText = \"Leave\",\n    CancelText = \"Stay\",\n    Destructive = true,\n})\n\nhandle.Completed:Connect(function(result)\n    if result.Confirmed then\n        print(\"Player confirmed\")\n    else\n        print(\"Prompt ended:\", result.Reason)\n    end\nend)\n```\n\n### Example: alert prompt\n\n`Alert` creates a single-action prompt.\n\n```lua\nprompts:Alert({\n    Title = \"Inventory full\",\n    Message = \"Remove an item before collecting another one.\",\n    ButtonText = \"Got it\",\n    ButtonStyle = \"Primary\",\n})\n```\n\n### Example: arbitrary actions\n\nUse `Open` when the prompt needs more than a simple confirmation.\n\n```lua\nlocal handle = prompts:Open({\n    Title = \"Unsaved changes\",\n    Message = \"Choose what should happen before leaving.\",\n\n    Actions = {\n        {\n            Id = \"cancel\",\n            Text = \"Stay here\",\n            Style = \"Secondary\",\n            IsCancel = true,\n        },\n        {\n            Id = \"discard\",\n            Text = \"Discard\",\n            Style = \"Danger\",\n        },\n        {\n            Id = \"save\",\n            Text = \"Save and leave\",\n            Style = \"Primary\",\n            IsDefault = true,\n        },\n    },\n})\n\nlocal result = handle:Await()\nprint(result.Action, result.Reason)\n```\n\n## ⚙️ API\n\n### `ModalKit.new(config?)`\n\nCreates a prompt manager. The manager must be created from a client script.\n\n```lua\nlocal prompts = ModalKit.new({\n    Parent = playerGui,\n    ScreenGuiName = \"GameModals\",\n    ResetOnSpawn = false,\n    DefaultPolicy = \"Queue\",\n\n    Theme = {\n        DialogMaxWidth = 620,\n    },\n})\n```\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `Parent` | `Instance?` | `LocalPlayer.PlayerGui` | Parent used for the generated `ScreenGui`. If a `ScreenGui` is supplied, its parent is used. |\n| `ScreenGuiName` | `string?` | `\"ModalKit\"` | Name of the generated `ScreenGui`. |\n| `ResetOnSpawn` | `boolean?` | `false` | Whether the generated `ScreenGui` resets when the player respawns. |\n| `DefaultPolicy` | `\"Queue\" \\| \"Replace\" \\| \"Reject\"` | `\"Queue\"` | Behavior when another prompt is already active. |\n| `Theme` | `table?` | Default theme | Global theme overrides. |\n\nModalKit creates the `ScreenGui` lazily when the first prompt opens. It owns and destroys that GUI when the manager is destroyed. A caller-provided `ScreenGui` is left intact.\n\n### `prompts:Confirm(options?)`\n\nCreates a two-action confirmation prompt and returns a `PromptHandle`.\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ConfirmText` | `string?` | `\"Confirm\"` | Confirm button text. |\n| `CancelText` | `string?` | `\"Cancel\"` | Cancel button text. |\n| `ConfirmActionId` | `string?` | `\"confirm\"` | Result ID for the confirm action. |\n| `CancelActionId` | `string?` | `\"cancel\"` | Result ID for the cancel action. |\n| `ConfirmStyle` | `string?` | `\"Primary\"` | Confirm button style. |\n| `CancelStyle` | `string?` | `\"Secondary\"` | Cancel button style. |\n| `Destructive` | `boolean?` | `false` | Uses the `Danger` style when no explicit confirm style is supplied. |\n| `OnConfirm` | `function?` | — | Callback for the explicit confirm action. |\n| `OnCancel` | `function?` | — | Callback for the explicit cancel action. |\n\n`OnCancel` is called only when the cancel action itself is activated. Other dismissal paths produce separate result reasons.\n\n### `prompts:ConfirmAsync(options?)`\n\nYields until a confirmation finishes and returns `(confirmed, result)`.\n\n```lua\nlocal confirmed, result = prompts:ConfirmAsync({\n    Title = \"Reset settings?\",\n    Message = \"Your local preferences will return to their defaults.\",\n    ConfirmText = \"Reset\",\n    Destructive = true,\n})\n```\n\n### `prompts:Alert(options?)`\n\nCreates a single-action prompt and returns a `PromptHandle`.\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ActionId` | `string?` | `\"ok\"` | Result ID for the alert action. |\n| `ButtonText` | `string?` | `\"Okay\"` | Visible button text. |\n| `ButtonStyle` | `string?` | `\"Primary\"` | Button style. |\n\n### `prompts:Open(options)`\n\nCreates a generic action prompt. If no actions are supplied, ModalKit creates a single `Okay` action.\n\n### `prompts:CloseAll(reason?)`\n\nCompletes every queued and active prompt with the supplied reason, or `\"Programmatic\"` when no reason is supplied. The manager remains usable afterward.\n\n### `prompts:Destroy()`\n\nCloses remaining prompts with reason `\"Destroyed\"`, disconnects UI events, destroys manager signals, and removes the generated `ScreenGui`.\n\n### `ModalKit.GetDefaultTheme()`\n\nReturns a fresh copy of ModalKit's default theme. Pass the copy to `ModalKit.new` or modify it before using it as a theme override.\n\n```lua\nlocal theme = ModalKit.GetDefaultTheme()\ntheme.DialogMaxWidth = 640\n\nlocal prompts = ModalKit.new({\n    Theme = theme,\n})\n```\n\n### Prompt handles\n\nAll prompt creation methods return a handle representing that request.\n\n| Method | Description |\n| --- | --- |\n| `handle:Await()` | Yields until the prompt finishes and returns its result. |\n| `handle:Update(patch)` | Updates queued or active prompt data. |\n| `handle:Respond(actionId)` | Programmatically activates an action on the active prompt. |\n| `handle:Close(reason?)` | Dismisses an active or queued prompt. The default reason is `\"Programmatic\"`. |\n| `handle:Destroy()` | Closes an unfinished prompt with `\"Destroyed\"` and destroys its signals. |\n| `handle:GetState()` | Returns `\"Queued\"`, `\"Opening\"`, `\"Open\"`, `\"Closing\"`, or `\"Closed\"`. |\n| `handle:GetResult()` | Returns the completed `PromptResult`, if available. |\n| `handle:IsOpen()` | Returns whether the prompt is active. |\n| `handle:IsFinished()` | Returns whether the prompt has completed. |\n\n```lua\nhandle.ActionTriggered:Connect(function(actionId, activeHandle)\n    print(\"Action:\", actionId)\nend)\n\nhandle.Completed:Connect(function(result)\n    print(\"Closed because:\", result.Reason)\nend)\n```\n\n## Complete options reference\n\nThese options are accepted by `Open` and can also be used with the helper methods where applicable.\n\n### Prompt options\n\n| Option | Type | Description |\n| --- | --- | --- |\n| `Title` | `string?` | Header text. |\n| `Message` | `string?` | Main wrapped message. |\n| `Icon` | `string?` | Optional image asset URI. |\n| `Actions` | `{Action}?` | Actions for a generic prompt. |\n| `ActionLayout` | `\"Auto\" \\| \"Horizontal\" \\| \"Vertical\"` | Button layout. `Auto` selects a layout based on the action set. |\n| `Policy` | `\"Queue\" \\| \"Replace\" \\| \"Reject\"` | Per-prompt overlap policy. |\n| `Dismiss` | `boolean \\| table` | Enables or configures non-action dismissal. |\n| `Animate` | `boolean?` | Set to `false` to disable transitions. |\n| `AnimationDuration` | `number?` | Overrides the transition duration. |\n| `Loading` | `boolean?` | Prevents action activation while true. |\n| `Content` | `function?` | Builds custom body content. |\n| `Theme` | `table?` | Per-prompt theme overrides. |\n| `OnOpen` | `function?` | Called after the prompt becomes active. |\n| `OnAction` | `function?` | Called as `(actionId, handle)`. |\n| `OnClose` | `function?` | Called as `(result, handle)`. |\n\n### Action fields\n\n```lua\n{\n    Id = \"save\",\n    Text = \"Save and leave\",\n    Style = \"Primary\",\n    IsDefault = true,\n    IsConfirm = true,\n    AutoClose = true,\n}\n```\n\n| Field | Type | Default | Description |\n| --- | --- | --- | --- |\n| `Id` | `string?` | Action index | Unique action/result identifier. |\n| `Text` | `string?` | `Id` | Visible button text. |\n| `Style` | `string?` | `\"Secondary\"` | Theme style name. |\n| `IsDefault` | `boolean?` | Automatic | Default keyboard/gamepad action. |\n| `IsConfirm` | `boolean?` | `false` | Marks its result as confirmed. |\n| `IsCancel` | `boolean?` | `false` | Marks its result as cancelled. |\n| `Disabled` | `boolean?` | `false` | Disables interaction with the action. |\n| `AutoClose` | `boolean?` | `true` | Set to `false` to keep the prompt open after activation. |\n| `Callback` | `function?` | — | Called as `Callback(handle, action)` after activation. |\n\nAction IDs must be unique within a prompt. If no enabled action is explicitly the default, ModalKit selects the last enabled action.\n\n## Behavior\n\n### Dismissal\n\nAll standard dismissal methods are enabled by default.\n\n```lua\nDismiss = {\n    CloseButton = true,\n    Backdrop = true,\n    Escape = true,\n    GamepadBack = true,\n}\n```\n\nDisable every non-action dismissal path with `Dismiss = false`, or configure the paths individually. Each path produces a distinct `Reason`.\n\n### Prompt overlap policies\n\nWhen a prompt opens while another is active, `Policy` determines what happens:\n\n- `\"Queue\"` stores the prompt and opens it after earlier prompts finish. This is the default.\n- `\"Replace\"` closes the current prompt with reason `\"Replaced\"` and places the new prompt at the front of the queue.\n- `\"Reject\"` completes the new handle with reason `\"Rejected\"` without displaying it.\n\n### Loading and runtime updates\n\nSet an action's `AutoClose` to `false` to keep the prompt open while work continues. Use `Update` to change the message, loading state, actions, content, or theme.\n\n```lua\nlocal prompt = prompts:Open({\n    Title = \"Publish build?\",\n    Message = \"The build will become visible to players.\",\n    Actions = {\n        {\n            Id = \"publish\",\n            Text = \"Publish\",\n            Style = \"Primary\",\n            IsDefault = true,\n            AutoClose = false,\n        },\n    },\n})\n\nprompt.ActionTriggered:Connect(function(actionId)\n    if actionId == \"publish\" then\n        prompt:Update({\n            Message = \"Publishing…\",\n            Loading = true,\n        })\n    end\nend)\n```\n\nWhile a prompt is loading, action responses are ignored.\n\n### Lifecycle callbacks and manager signals\n\n`OnOpen`, `OnAction`, and `OnClose` callbacks can be supplied in prompt options. They are protected and run asynchronously so callback errors do not interrupt ModalKit's cleanup.\n\n```lua\nprompts:Open({\n    Title = \"Example\",\n    OnOpen = function(handle)\n        print(\"Opened\", handle.Id)\n    end,\n    OnAction = function(actionId, handle)\n        print(\"Action\", actionId, \"on\", handle.Id)\n    end,\n    OnClose = function(result, handle)\n        print(\"Closed\", handle.Id, result.Reason)\n    end,\n})\n```\n\nA manager also exposes lifecycle signals for every prompt it owns:\n\n```lua\nprompts.PromptOpened:Connect(function(handle)\n    print(\"Opened\", handle.Id)\nend)\n\nprompts.ActionTriggered:Connect(function(handle, actionId)\n    print(\"Action\", actionId, \"on prompt\", handle.Id)\nend)\n\nprompts.PromptClosed:Connect(function(handle, result)\n    print(\"Closed\", handle.Id, result.Reason)\nend)\n```\n\n### Custom content\n\nUse `Content` to insert arbitrary runtime-generated UI into the body of a prompt.\n\n```lua\nprompts:Confirm({\n    Title = \"Purchase item?\",\n    Message = \"Review the purchase before continuing.\",\n\n    Content = function(container, handle)\n        local summary = Instance.new(\"TextLabel\")\n        summary.BackgroundTransparency = 1\n        summary.AutomaticSize = Enum.AutomaticSize.Y\n        summary.Size = UDim2.new(1, 0, 0, 0)\n        summary.Text = \"Crystal Sword — 500 coins\"\n        summary.TextWrapped = true\n        summary.Parent = container\n\n        return function()\n            -- Disconnect custom events or release other resources here.\n        end\n    end,\n})\n```\n\nA content builder may return a cleanup function, an unparented `Instance` for ModalKit to parent, or nothing when it handles parenting itself. Updating `Content` rebuilds the section and runs the previous cleanup path first.\n\n### Themes\n\nGet a fresh copy of the default theme or provide only the properties to override.\n\n```lua\nlocal prompts = ModalKit.new({\n    Theme = {\n        DialogColor = Color3.fromRGB(246, 247, 251),\n        TitleColor = Color3.fromRGB(25, 27, 34),\n        MessageColor = Color3.fromRGB(73, 77, 91),\n        Styles = {\n            Primary = {\n                BackgroundColor = Color3.fromRGB(120, 80, 255),\n            },\n        },\n    },\n})\n```\n\nBuilt-in action styles are `Primary`, `Secondary`, `Danger`, `Success`, and `Neutral`. Additional named styles can be defined under `Theme.Styles` and referenced by an action's `Style` field.\n\n### Input behavior\n\n- Buttons use `Activated`, covering mouse, touch, and gamepad activation.\n- Enter and keypad Enter trigger the default action.\n- Keyboard activation is ignored while a `TextBox` is focused.\n- Escape, gamepad B, and backdrop activation dismiss the prompt when enabled.\n- Gamepad selection moves to the default enabled action where applicable.\n- The previously selected GUI object is restored after the modal closes when it still exists.\n\n### Prompt results\n\nA completed prompt produces a result table:\n\n```lua\n{\n    Action = \"confirm\", -- nil when no action was selected\n    Reason = \"Action\",\n    Confirmed = true,\n    Cancelled = false,\n}\n```\n\nBuilt-in reasons are `Action`, `CloseButton`, `Backdrop`, `Escape`, `GamepadBack`, `Programmatic`, `Replaced`, `Rejected`, and `Destroyed`. Custom strings supplied to `Close` or `CloseAll` are preserved. `Confirmed` and `Cancelled` follow the action's `IsConfirm`/`IsCancel` flags or the conventional IDs `\"confirm\"`/`\"cancel\"`.\n\n## 📝 Notes\n\n","readmeTruncated":true,"readmeFull":"https://api.forest.dev/v1/package/biotoxin495/roblox/modalkit/1.0.1/readme"}