No need, this is actually working as intended. The whole reason why the TextEditable
property of TextBox
es exist is so that the developer can allow users to still click into the textbox to highlight and copy text, being able to paste it elsewhere. In this case, Developer is using TextBox
es because they are relying off of the TextBox.PlaceholderText
text to display when the TextBox
is empty, which is bad practice. Ideally, Developer should use TextLabel
s combined with a check in Lua to replace empty text with the placeholder text. TextBox
es are designed for user input, not displaying text just for viewing. By requesting the user for input when clicked, they are doing their job as intended.
An example of how this can be implemented in Lua, assuming label
references the TextLabel
:
-- define our TextLabel. Sign is just a part in the workspace.
local label = workspace:WaitForChild("Sign"):WaitForChild("SurfaceGui"):WaitForChild("TextLabel")
-- define text strings and colors
-- these can also be StringValues/Color3Values under your TextLabel
local placeholderText = "Placeholder text"
local textColor = Color3.new(0,0,0)
local placeholderColor = Color3.new(1,0,1)
-- do some stuff to get to this point, of course
local textString = "asdasd"
label.Text = textString ~= "" and textString or placeholderText
label.TextColor3 = textString ~= "" and textColor or placeholderColor
In the above example, if the textString
value is an empty string, the label’s text will be set to the value of placeholderText
.
Another example of how this can be achieved, you can just put this anywhere in your script, and not have to worry about doing this every time you want to change the label’s text:
-- define our TextLabel
local label = workspace:WaitForChild("Sign"):WaitForChild("SurfaceGui"):WaitForChild("TextLabel")
-- define text strings and colors
local placeholderText = "Placeholder text"
local textColor = Color3.new(0,0,0)
local placeholderColor = Color3.new(1,0,1)
label:GetPropertyChangedSignal("Text"):Connect(function()
local labelText = label.Text
label.Text = labelText ~= "" and labelText or placeholderText
label.TextColor3 = labelText ~= "" and textColor or placeholderColor
end)
Finally, a complex example of recursively searching the whole workspace for TextLabel
s with a StringValue
named PlaceholderText
. This allows each label to have it’s own unique placeholder text and (optionally) a Color3Value
named PlaceholderColor3
. Additionally, this handles unexpected parenting/reparenting and changing the placeholder text/color during gameplay (will not update in realtime, but rather the next time the label changes.) If the PlaceholderText
value is removed, it will unbind the function. This also means that the color will stop updating, so if you had the placeholder text showing with a custom color, updating the label will not change it if the PlaceholderText
is removed. This is intentional, as it allows you to do as they please with the label without getting any interference from the script if you remove the value from the label.
-- here, we have the ability to selectively put a StringValue on labels which we
-- want there to be a placeholder text on. For the optional, placeholder color,
-- we can include a Color3Value on the label as well
-- this table stores the labels' initial colors
-- we do this so that once it is changed to the placeholder color, it can go back
local initialColorStorage = {}
-- this function actually checks the text label and analyzes it on change,
-- giving it a placeholder text if empty
local function CheckTextLabel(label)
local labelText = label.Text
-- this code looks complicated because I don't want it to error
-- if the values somehow get removed from the label
local placeholderTextValue = label:FindFirstChild("PlaceholderText")
label.Text = labelText ~= "" and labelText or (placeholderTextValue and placeholderTextValue.Value or "")
if initialColorStorage[label] then
local placeholderColorValue = label:FindFirstChild("PlaceholderColor3")
label.TextColor3 = labelText ~= "" and initialColorStorage[label] or (placeholderColorValue and placeholderColorValue.Value or label.TextColor3)
end
end
-- set up the event listeners based off of the value
local function SetupBindings(object, value)
if value.Name == "PlaceholderText" then
local binding = object:GetPropertyChangedSignal("Text"):Connect(function()
CheckTextLabel(object)
end)
object.ChildRemoved:Connect(function(removed)
if removed == value then
binding:Disconnect()
end
end)
object.Destroying:Connect(function()
binding:Disconnect()
end)
elseif value.Name == "PlaceholderColor3" then
initialColorStorage[object] = object.TextColor3
end
end
-- add event listeners to when a new object is added
local function ListenForChildren(object)
object.ChildAdded:Connect(function(child)
SetupBindings(object, child)
end)
end
-- check object to see if it is a TextLabel/TextButton, if it is, continue
-- added TextButton in case you want to make the sign's label clickable
local function CheckObject(object)
if object.ClassName == "TextLabel" or object.ClassName == "TextButton" then
if object:FindFirstChild("PlaceholderText") then
if object:FindFirstChild("PlaceholderColor3") then
initialColorStorage[object] = object.TextColor3
end
SetupBindings(object, object.PlaceholderText)
end
-- listen for new children due to replication lag or for changes
ListenForChildren(object)
end
end
-- function recursively searches workspace for any labels with PlaceholderText
-- string value. if found, it will continue with the script
local function RecursiveSearchTextLabels(object)
local children = object:GetChildren()
for i = 1, #children do
local child = children[i]
CheckObject(child)
RecursiveSearchTextLabels(child)
end
end
-- final function that sets up everything
local function SetupEverything(object)
-- listen for descendants being added. this is due to replication lag
object.DescendantAdded:Connect(function(object)
if object.ClassName == "TextLabel" or object.ClassName == "TextButton" then
CheckObject(object)
end
end)
-- recursively search for already existing labels
RecursiveSearchTextLabels(object)
end
-- where to search for labels. if you don't want it searching the entire
-- workspace, but rather a subsection of it, you can define where to search here
-- for example: workspace:WaitForChild("LandPlots"):WaitForChild("Signs")
SetupEverything(workspace)
I know that this isn’t as simple as using the TextBox
hack, but sometimes, doing things the proper way is worth it in the long run for user experience. Say Roblox were to, in the future, add the ability for VR controllers to interact with TextBox
es on SurfaceGui
s. Your users would probably be confused as to why a virtual keyboard pops up when they hover their controller over your TextBox
sign. Doing things the proper way mitigates these issues and your users will thank you.
edit: Updated functions to be local instead of global (which is faster), had to reorder functions