The combination of functions to move the cursor, select text color, and read a key made most parts of a UI sufficiently easy to design and implement that there was far less need for automated tools than when designing a windowed application. About the only thing missing would have been a good function to accept an input line with cursor editing, but there were plenty of those around and they were generally agnostic to the design of the application using them, so a programmer who had a line-input library he liked could simply use it.
One might use a screen-layout-design program to do mockups of a screen, though a text editor could also suffice if one didn't need color. Once one identified the positions of prompts input fields, it wasn't that hard to write code to handle it.
An essential feature of text-mode user interfaces compared with windowed ones is that operational flows were much more linear. If one didn't mind using "goto", a typical user interface would essentially be:
GetField1:
...
GetField2:
ExitKey := ReadInputLine( { parameters for a particular input operation } );
If { exit key indicates premature exit } Then Goto Exit;
If { exit key indicates go back to previous field } then Goto GetField1;
GetField3:
...
etc. While windowed applications needed to handle many more kinds of things that could happen while editing a text field, text-mode apps without mouse support generally didn't. Further, if one had a working app without mouse support, it may have been practical to tweak ReadInputLine to add mouse support with relatively minimal change to the surrounding application(*), whereas using an application framework with mouse support built in would have required major rewrites.
(*) If an application was designed so that pushing tab would cycle through fields in wrapping fashion, a ReadInputLine
function could respond to a mouse click by latching the coordinates of an unacknowledged mouse click and the identity of the field that was active when it occurred. It could then, while an unacknowledged mouse click remained pending, check to see whether it was within the current input field and, if not, hit tab and notice if the field that became active was the one that had been active when the mouse was clicked. If so, call a user "Unacknowledged mouse click here" function with the coordinates of the click, and clear the pending click, leaving the field selection back where it started, without the underlying application having to know or care about what was happening.