I’ve found managing spacing to be a little annoying with borders. The biggest issue in my opinion is that they are rendered around the box, not inside. I think your display would be fixed if they were rendered inside.
My biggest question with “BorderScaled” solution is what do we scale with respect to? If I have a border of 10 and a size of UDim2.new(0.5, 0, 0.5, 0), when should the border equal 10 exactly? For some “baseline” 1280x720 screen? That doesn’t feel like a great solution.
If you instead specify some percentage of the total screen for the border (ie, BorderSize = UDim.new(.01, 0)), it’ll take up 1% of screen space on both axes. Presumably, this would pay attention to SizeConstraint. This still doesn’t feel like a good solution, as why would one specify their border as a changing number? It makes sense on your top-level guis, but the buttons within it should probably maintain 1px borders no matter what their size is. This probably adds more complexity than is worth it for a simple “border” object.
I think four frames is a decent solution and I wouldn’t call it a hack. I would continue using that.
Also, if you’re looking to cut down the frames, another solution would be this:
Frame
: BackgroundTransparency = 1
: Size = some scaling size
Frame
: Size = UDim2.new(1, -20, 1, -20)
: Position = UDim2.new(0, 10, 0, 10)
: BorderSizePixel = 10
: Position = UDim2.new(0, 10, 0, 10)
Finally, if it weren’t for the fact that people designed their Guis with borders on the outside, I would say they should be changed to render on the inside of frames. A frame with a size of {{0, 40}, {0, 40}} and a border size of 4 should have an effective space of {{0, 32}, {0, 32}}. However, this in itself might be a disadvantage. While I would have an easier time working with this, I couldn’t really say it’s distinctly better.