Toast Notifications

(Before reading please note that a toast is kinda like a notification)

Version 2.0

Hello everyone! In Roblox, toast notifications exist like the one showed below. The problem is, Roblox doesn’t actually allow you to make them your self. ToastNotificationService is a thing but it can only be used by roblox scripts, so it’s pretty much useless.

image


So that brings us to a module I made this morning. This module perfectly replicates how toasts look normally. And it’s pretty easy to use. Below shows an image of how a toast looks with the module.

image

The toast has both opening and closing animations that perfectly mimic roblox toasts.

Sorry for bad quality

–Documentation–


To get the module working in your script, first reference it.

local ToastNotifications = require(game.ReplicatedStorage.ToastNotifications)

To create a new toast, use ‘ToastNotifications.CreateToast()’

  • toast = ToastNotifications.CreateToast(ToastId, TopText, BottomText, IconId, DisplayTime)

  • ToastNotifications.GetToast(id) Gets a toast by the ‘id’ property

  • ToastNotifications.CancelAllToasts() Cancels all active toasts.

  • toast.DestroyToast(delaytime) Cancels the toast without playing the closing animation with optional added delay.

  • toast.CancelToast() Cancels the toast and plays the closing animation.

  • toast.HideToast() Changes the visible property of the toast to false.

  • toast.ShowToast() Changes the visible property of the toast to true.


The example script below shows a toast that will open when the player press the ‘E’ key. When the player lets go it will cancel the toast.

local Player = game.Players.LocalPlayer

local globalToast = nil

game:GetService("UserInputService").InputBegan:Connect(function(key)
	if key.KeyCode == Enum.KeyCode.E then
		globalToast = Toast.CreateToast(Player,"Toast Test","Yummy toast.",'rbxassetid://7255117720')
	end
end)

game:GetService("UserInputService").InputEnded:Connect(function(key)
	if key.KeyCode == Enum.KeyCode.E then
		globalToast.CancelToast()
	end
end)

In all, it probably has tooken me longer to write this post than to actually make the module. But if you want to use it in any of your projects, you can. If you find any problems, please tell me and I will have them fixed as soon as possible Thanks!

62 Likes

Wow! This is incredible, Could you explain more about how you mimicked the Roblox UI so precisely? Thanks!

5 Likes

Just browse the source:

6 Likes

Oh neat, the other day I was browsing through the API and found ToastNotificationService and wished I could use it

My only critique is that the real ToastNotificationService API allows you to set a notificationId string and then use it for different functions similar to BindToRenderStep/UnbindFromRenderStep so you don’t have to keep a variable for it

3 Likes

That’s nice! This is very helpful for the community. I was so confused though when you said “Toast notifications” as I was like, “notifications for me? Nice!”

11 Likes

I was thinking about that, I might implement that later…

3 Likes

Simple copy and paste from the player gui for the ui. As for the animations, I found the code for it within the ‘CameraUI’ script.

1 Like

Hello developers! I’m happy to announce update 2.0. This update brings two new functions. We now have the ‘GetToast’ function and the ‘CancelAllToasts’ function. Every toast now will be set with an id, like so.

  • CreateToast(ToastId, TopText, BottomText, IconId, DisplayTime)
  • GetToast(id)
  • CancelAllToasts()

This update allows you to reference toasts anywhere in your script. I still recommend using a variable to hold your toast as the new id system is a bit buggy but it shouldn’t be a problem in most cases. Thanks for all your suggestions!

@maycoleee2231

6 Likes

Thank you for this resource! I’m using it for in-game notifications and it’s served really useful

3 Likes

Thanks for this, really useful. I did some editing and made the notifs stack in a list. Here’s the code if anybody wants it. ( I didn’t add any toast image for the screenshot below )
image

local module = {}

function AnimateToast(toast,anim)
	local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
	local open = anim == "open"

	local imgParams = {ImageTransparency = open and 0 or 1}
	local txtParams = {TextTransparency = open and 0 or 1}

	game.TweenService:Create(toast, tweenInfo, {
		Size = open and UDim2.new(0, 326, 0, 58) or UDim2.new(0, 80, 0, 58), ImageTransparency = open and 0.4 or 1
	}):Play()

	game.TweenService:Create(toast.IconBuffer.Icon, tweenInfo, imgParams):Play()
	game.TweenService:Create(toast.TextBuffer.Upper, tweenInfo, txtParams):Play()
	game.TweenService:Create(toast.TextBuffer.Lower, tweenInfo, txtParams):Play()

	toast = not open and game.Debris:AddItem(toast,0.25)
end

function GetGui()
	local Player = game.Players.LocalPlayer
	local screenGui = Player.PlayerGui:FindFirstChild("Toasts") or Instance.new("ScreenGui",Player.PlayerGui)
	screenGui.DisplayOrder = 2147483647
	screenGui.Name = "Toasts"
	
	if screenGui:FindFirstChild("Frame") then
		return screenGui:FindFirstChild("Frame") 
	else
		local Frame = Instance.new("Frame", screenGui)
		Frame.BackgroundTransparency = 1
		Frame.Size = UDim2.new(0, 500,0, 500)
		Frame.AnchorPoint = Vector2.new(0.5, 0)
		Frame.Position = UDim2.new(0.5,0,0.03,0)
		
		local uiListLayout = Instance.new("UIListLayout", Frame)
		uiListLayout.Padding = UDim.new(0, 5)
		uiListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
		
		return Frame
	end
end

module.CreateToast = function(ToastId,TopText,BottomText,IconId,DisplayTime)
	local newToast = script.Toast:Clone()
	newToast.TextBuffer.Upper.Text = TopText
	newToast.TextBuffer.Lower.Text = BottomText
	newToast.IconBuffer.Icon.Image = IconId
	newToast.Name = ToastId
	
	local oldToast = GetGui():FindFirstChild(ToastId)
	local run = false 
	
	if oldToast then
		run = oldToast.ImageTransparency < 0.4
	end
	
	if not run then
		game.Debris:AddItem(newToast,DisplayTime or math.huge)
		AnimateToast(newToast,"open")
		newToast.Parent = GetGui()
		
		return {
			CancelToast = function()
				AnimateToast(newToast,"cancel")
			end;
			DestroyToast = function(delaytime : 'optional')
				game.Debris:AddItem(newToast,delaytime or 0)
			end;
			HideToast = function()
				newToast.Visible = false
			end;
			ShowToast = function()
				newToast.Visible = true
			end;
		}
	else
		return {CancelToast = empty(),DestroyToast = empty(),HideToast = empty(),ShowToast = empty()}
	end
end

function empty()
	return function() end
end

module.GetToast = function(id)
	local gui = GetGui()
	local toast = gui:FindFirstChild(id)
	
	if toast then
		return {
			CancelToast = function()
				AnimateToast(toast,"cancel")
			end;
			DestroyToast = function(delaytime : 'optional')
				game.Debris:AddItem(toast,delaytime or 0)
			end;
			HideToast = function()
				toast.Visible = false
			end;
			ShowToast = function()
				toast.Visible = true
			end;
		}
	else
		return {CancelToast = empty(),DestroyToast = empty(),HideToast = empty(),ShowToast = empty()}
	end
end

module.CancelAllToasts = function()
	for i,toast in pairs(GetGui():GetChildren()) do
		AnimateToast(toast,"cancel")
	end
end

return module

Would be cool if the notifs automatically increase in height if the text is too long

6 Likes

Wow this is really cool! I never thought of stacking notifications even though that would make a lot more sense. Awesome creation!

2 Likes

Nothin’ major, but added notification sounds, change color of icons, sorting of layout order automatically, ability to limit the amount of toasts displayed at one time. Enjoy.

local PlayersService = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local SoundService = game:GetService("SoundService")
local DebrisService = game:GetService("Debris")

local module = {}
local MAX_TOASTS = 5 -- Maximum number of toasts displayed at one time
local activeToasts = {} -- Table to store active toast instances

function AnimateToast(toast, anim)
	-- Notification sound
	local sound = Instance.new("Sound", toast)
	sound.SoundId = "rbxassetid://6026984224"
	sound.SoundGroup = SoundService.BassBoost
	sound.RollOffMode = Enum.RollOffMode.LinearSquare
	sound.Volume = 5

	local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0)
	local open = anim == "open"

	local imgParams = {ImageTransparency = open and 0 or 1}
	local txtParams = {TextTransparency = open and 0 or 1}

	if sound.Loaded then
		sound:Play()
	end
	
	local toastSize = open and UDim2.new(0, 326, 0, 58) or UDim2.new(0, 0, 0, 0)
	local imgTransparency = open and 0.4 or 1

	local iconTween = TweenService:Create(toast.IconBuffer.Icon, tweenInfo, imgParams)
	local upperTextTween = TweenService:Create(toast.TextBuffer.Upper, tweenInfo, txtParams)
	local lowerTextTween = TweenService:Create(toast.TextBuffer.Lower, tweenInfo, txtParams)
	local toastTween = TweenService:Create(toast, tweenInfo, {Size = toastSize, ImageTransparency = imgTransparency})

	toastTween:Play()
	iconTween:Play()
	upperTextTween:Play()
	lowerTextTween:Play()

	if not open then
		-- Add the toast to debris for removal
		DebrisService:AddItem(toast, 0.25)
		activeToasts[toast] = nil -- Remove the toast from active toasts
		
		-- Create a new array with remaining toasts
		local remainingToasts = {}
		for toast in pairs(activeToasts) do
			table.insert(remainingToasts, toast)
		end

		-- Sort the array based on LayoutOrder
		table.sort(remainingToasts, function(a, b) return a.LayoutOrder < b.LayoutOrder end)

		-- Rearrange LayoutOrder values of the remaining toasts
		for i, t in ipairs(remainingToasts) do
			t.LayoutOrder = i
		end
	end
end

function GetGui()
	local Player = PlayersService.LocalPlayer
	local screenGui = Player:WaitForChild("PlayerGui", 5):FindFirstChild("Toasts") or Instance.new("ScreenGui", Player.PlayerGui)

	screenGui.DisplayOrder = 2147483647
	screenGui.Name = "Toasts"
	screenGui.IgnoreGuiInset = true

	if screenGui:FindFirstChild("Frame") then
		return screenGui:FindFirstChild("Frame") 
	else
		local Frame = Instance.new("Frame", screenGui)
		Frame.BackgroundTransparency = 1
		Frame.Size = UDim2.new(0, 500, 0, 500)
		Frame.AnchorPoint = Vector2.new(0.5, 0)
		Frame.Position = UDim2.new(0.5, 0, 0.03, 0)

		local uiListLayout = Instance.new("UIListLayout", Frame)
		uiListLayout.Padding = UDim.new(0, 5)
		uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
		uiListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center

		return Frame
	end
end

module.CreateToast = function(ToastId, TopText, BottomText, IconId, IconColor, DisplayTime)
	local newToast = script.Toast:Clone()
	newToast.TextBuffer.Upper.Text = TopText
	newToast.TextBuffer.Lower.Text = BottomText
	newToast.IconBuffer.Icon.Image = IconId
	newToast.IconBuffer.Icon.ImageColor3 = IconColor or Color3.fromRGB(255, 255, 255)
	newToast.Name = ToastId
	newToast.LayoutOrder = #activeToasts + 1

	local oldToast = GetGui():FindFirstChild(ToastId)
	local run = false

	if oldToast then
		run = oldToast.ImageTransparency < 0.4
	end

	if not run then
		DebrisService:AddItem(newToast, DisplayTime or math.huge)
		AnimateToast(newToast, "open")
		newToast.Parent = GetGui()

		activeToasts[newToast] = true -- Add the new toast to the active toasts table
		CheckMaxToasts() -- Check if the maximum number of toasts is exceeded
		UpdateToastPositions() -- Update the positions of the toasts

		return {
			CancelToast = function()
				AnimateToast(newToast, "cancel")
			end;

			DestroyToast = function(delaytime)
				DebrisService:AddItem(newToast, delaytime or 0)
			end;

			HideToast = function()
				newToast.Visible = false
			end;

			ShowToast = function()
				newToast.Visible = true
			end;
		}
	else
		return {CancelToast = empty(), DestroyToast = empty(), HideToast = empty(), ShowToast = empty()}
	end
end

function empty()
	return function() end
end

module.GetToast = function(id)
	local gui = GetGui()
	local toast = gui:FindFirstChild(id)

	if toast then
		return {
			CancelToast = function()
				AnimateToast(toast, "cancel")
			end;

			DestroyToast = function(delaytime)
				DebrisService:AddItem(toast, delaytime or 0)
			end;

			HideToast = function()
				toast.Visible = false
			end;

			ShowToast = function()
				toast.Visible = true
			end;
		}
	else
		return {CancelToast = empty(), DestroyToast = empty(), HideToast = empty(), ShowToast = empty()}
	end
end

module.CancelAllToasts = function()
	for i, toast in pairs(GetGui():GetChildren()) do
		AnimateToast(toast, "cancel")
	end
end

function UpdateToastPositions()
	local gui = GetGui()
	local children = gui:GetChildren()
	local yPos = 0

	for i, toast in ipairs(children) do
		-- Check if the toast is an instance of Frame, which has a Position property
		if toast:IsA("Frame") then
			toast.Position = UDim2.new(0, 0, 0, yPos)
			yPos = yPos + toast.Size.Y.Offset
		end
	end
end

function CheckMaxToasts()
	local gui = GetGui()
	local children = {}

	-- Add only ImageLabel instances to the children table
	for _, child in ipairs(gui:GetChildren()) do
		if child:IsA("ImageLabel") then
			table.insert(children, child)
		end
	end

	-- Sort children by LayoutOrder
	table.sort(children, function(a, b) return a.LayoutOrder < b.LayoutOrder end)

	while #children > MAX_TOASTS do
		AnimateToast(children[1], "cancel")
		table.remove(children, 1)
	end
end

return module
5 Likes

this is amazing! the ui is incredible. wish i found this earlier

Doesn’t seem to work properly, doesn’t stack notifs half the time.

It works perfectly in my use case. Maybe it is due to your setup?

I just realized I’ve been giving all the notifications the same ID :sweat_smile: Amazing!

Yeah, that’ll defo be the reason! Glad you resolved it. :slight_smile:

1 Like

It’s kinda crazy how this pretty badly coded port for toast notifications has gotten so much attention. I didn’t really know how to code that well at the time, so some things in this module just don’t make too much sense. Thanks for the support anyway!

1 Like