Published on August 17, 2025 under the Web category.
Writing HTML on a phone is difficult. To write HTML – like all programming languages – you need to use a lot of symbols. The main symbols that you need for HTML are the open and closing angle brackets (< and >, respectively).
On iOS, these are in the tertiary keyboard. You have to go to the keyboard with the numbers and common symbols then click the symbols button to see the angle brackets. Then you need to swap back to the main keyboard to type an element name, potentially move to the second keyboard for quotation marks if you want to add an attribute to your HTML element, and go back to add a close angle bracket.
Noticing this friction, I have been working on…
Published on August 17, 2025 under the Web category.
Writing HTML on a phone is difficult. To write HTML – like all programming languages – you need to use a lot of symbols. The main symbols that you need for HTML are the open and closing angle brackets (< and >, respectively).
On iOS, these are in the tertiary keyboard. You have to go to the keyboard with the numbers and common symbols then click the symbols button to see the angle brackets. Then you need to swap back to the main keyboard to type an element name, potentially move to the second keyboard for quotation marks if you want to add an attribute to your HTML element, and go back to add a close angle bracket.
Noticing this friction, I have been working on a concept for a mobile-first HTML editor. Its design is informed by the observation that switching between several keyboard contexts massively adds to the friction required to author a HTML document.
I have designed this tool with the goal that I could sketch HTML pages on the go. The goal is not to have a complete mobile development environment, rather a place to start exploring an idea.
In this blog post, I am going to walk through the concept I have so far, discussing both the design of the user experience and some of the technical internals. The editor is available for use as a single HTML file in my playground.
You can see the source code on GitHub.
I plan to move the code to an open source repository in the future, but the trie implementation I am using does not have a license so I have to assume all rights reserved on that part of the code. If you know of an open source JavaScript trie implementation, please let me know.
Here is a demo of the editor in use:
NB: In one part of the demo, the cursor shifts to the wrong place. Cursor position has been among the hardest parts of this tool to get right. I document the technical reasons why later in this post. I don’t usually document projects that aren’t fully usable, but I wanted to document the design and user experience I have been working on.
Using auto-suggest to reduce context switching
For years, iOS has had an auto-suggest feature that appears above the main keyboard. For example, given the text “On the”, iOS offers the suggestions “way”, “road”, and “day”:
ALT
The iOS keyboard with three suggestions on the top: “way”, “road”, and “day”.
This keyboard aims to predict what you may want to type next so that you can tap on a word to accept the suggestion rather than type full words. When I was thinking about how to reduce context shifting, I thought: what if I could design a similar auto-suggest feature but native to HTML?
There would be three buttons: each a suggestion. 1 When clicked, the suggestion would be added to the HTML editor.
The suggestions come from two places:
- A manually-written list of elements that may logically follow the previous element in the document. For example, if you add a
sectionelement, the keyboard will suggesth2andp, which you may want to use immediately after a section element. - Autocompleting the word you are typing. For example, if you type
bloc, the suggestionblockquotewill appear. The system that recommends elements that may logically follow a previous element may reduce the need to type any characters for an element. For example, if you addmain,h1andsectionare both suggested:
These elements are both useful to add immediately after main.
Suggestions are programmed in a JSON object with the following structure:
"main": {
"h1": 0.9,
"section": 0.8,
"/main": -1
},
The key name is the name of an element. The values are the elements that should be suggested given the key name. The numbers are the priority for each result. This is used to present suggestions in order. The closing tag is always last, with the -1 priority.
HTML tags that do not require a separate closing tag – for example, img uses <img />– are closed automatically. In this case, the last suggestion is left blank to indicate to the user that the element does not need to be closed.
Right now, the list of element recommendations given the previous element is manually-written, but this could be informed by statistics. I wanted to keep the design simple, recognising that the manually-written list, in testing, was already helping me write semantic HTML documents.
If the suggestions are not useful, you can start typing to see an autocomplete suggestion:
In this case, “blockquote” has appeared as the first item in the list of suggestions because I have started typing an element name.
CSS editing and previews
With the auto-suggest primitive implemented, I was able to start authoring HTML documents. With that said, two pieces of the puzzle were missing: (i) I couldn’t see my document rendered, and (ii) I couldn’t apply styles.
I came up with a solution: the web editor would have three screens. These are a CSS editor, a HTML editor, and a preview window. By default, the application would open in the HTML editor. You could then swipe left or right to navigate between the editors and the preview window. Here is an example showing the navigation between each pane on an iPhone:
Below I will talk about how the CSS and preview features work, and how I designed the swipe feature.
The CSS editor
The CSS editor uses the same autocomplete design as the HTML editor, except with different logic and contexts.
In the CSS editor, you can start typing a rule and get an autocomplete suggestion for an element name to use as a selector. The { character is present in the auto-suggest list if you are writing a selector. Here is an example of the suggestion list:
You can also type . to open the class menu to autocomplete a class name. I describe how this works in the “The element property context” section later in this blog post.
When you add a { character, the context shifts to recommend properties you can add. You can start typing to get a recommendation for CSS properties that begin with the words you are typing:
When you accept a suggestion, a colon and space are automatically added. For a very limited number of elements, suggestions are available (i.e. border will suggest 1px solid and 1px dotted).
The CSS editor is programmed to autocomplete a limited number of suggestions. The step required to add more is to add to the list of understood rules in the editor.
The preview window
When the preview window is opened, the HTML and CSS the user has typed is taken and set as the innerHTML value of an element in the preview window:
All of the user’s CSS is encapsulated as follows:
<style>
.preview {
[user css...]
}
</style>
This makes use of the nesting feature available in CSS. I still need to understand the nesting context more, but I think with this setup I am able to ensure that the user’s styles don’t interfere with the rest of the page.
The preview window has a button to copy all of the HTML and CSS to the clipboard. This could then be used to copy the code to paste somewhere else.
Switching modes
The application has two editing interfaces and one preview interface. They are ordered as such:
- CSS
- HTML
- Preview By default, the HTML mode is displayed.
These modes are all divs inside a parent container. The parent container has a maximum height and allows you to scroll left between modes. When you swipe, the interface will “snap” into the corresponding interface. The code uses the CSS scroll-snap-type rule to implement the snap effect.
Initially, from the HTML mode, the user could swipe left to CSS and right to preview. From the CSS mode, the user would need to swipe right once to go to the HTML editor or swipe right twice to go to the preview window.
Having to swipe twice to go from the CSS mode to the preview mode was not ideal. I decided that it would be optimal for the user to be able to swipe left on the CSS panel to get to the preview window. This would mean that the user is never more than one swipe away from the preview window.
Implementing this was tricky. I thought about how it could be accomplished with CSS, but I ended up realising that JavaScript was appropriate for the problem. I came up with the following algorithm:
- Determine what pane is in view (CSS, HTML, Preview).
- If the HTML pane is in view, re-order the parent container so that the CSS pane is to the left and the Preview pane is to the right in the editor DOM.
- If the CSS pane is in view, re-order the parent container so that the Preview pane is to the left and the HTML pane is to the right in the editor DOM.
- If the Preview pane is in view, re-order the parent container so that the HTML pane is to the left and the CSS pane is to the right in the editor DOM. This kind of “reactive re-ordering” has the responsibility of ensuring the correct panes are to the left and right of the current pane the user is viewing in the DOM. The end result is that the user can scroll left or right infinitely and the relevant panes will always be ready to display.
By reordering the DOM, I can continue to rely on the CSS scrolling functionality. I also don’t have to duplicate/remove and re-add elements. With this solution, I didn’t need to add any more CSS to make the in solution work.
The element property context
When authoring HTML, you may want to add a property to an element. For example, you may want to add a class to a section element for use in styling the element. This involves typing several symbols (i.e. class="").
This made me think that the editor could have a mode for adding properties. Right now, two properties are supported: ID and class.
When you add a landmark element – main or section, for example – a plus icon appears at the end of the keyboard:
When tapped, this shows the suggested properties. You can then click on one to add the property to the element before your cursor:
If you add the class property, the keyboard is then hidden and replaced with a list of classes already in your document, alongside a few pre-defined classes that you can use. This list is computed by taking your HTML, running it through the browser DOM API, and finding all the class names.
As you type, the list of suggested classes is narrowed to those that start with the same sequence of text you are typing.
The class autocomplete feature further reduces the amount of typing you have to do, especially for common classes.
I also added a colour context that triggers in CSS mode when you type the hashtag (#) character inside a CSS rule. This shows a list of all hex colours used in a document. You can tap on a colour to add it to the document:
How suggestions are computed, and what I would do differently
The autosuggestion feature uses the “trie” data structure. Tries, also known as prefix trees, allow you to efficiently find words that end in a given prefix.
To find what prefix to use, my code uses manual heuristics informed by my understanding of HTML and CSS. This is to say: there is a lot of manual logic that likely could be improved by consistently processing the content of the HTML and CSS editing fields as an abstract syntax tree.
Having an abstract syntax tree would allow me to both reliably process user input without having to maintain my own processing code that doesn’t follow the spec, and also would make it easier to understand what context a user is in. The latter would be useful for adding more autosuggestion contexts without the complexity of code I have right now.
Of note, the current implementation has several bugs that mean sometimes the system doesn’t move your cursor to the right position. These are hard to debug because of all the manual rules implemented right now.
I have a friend that says that they never complain about working code. This is how I feel right now: my implementation could be better, but this is as much an experiment in user experience and interaction design as it is in the internals. Indeed, interaction design motivated this project. But if you decide to make something like this, I recommend considering how a syntax tree could be used. I may one day substitute my code to use syntax trees, but this will require a lot of work.
Code formatting
When you accept a suggestion, the code in the editor you are viewing – either the HTML or CSS text area – will be formatted using a text formatter. This ensures that markup is consistently readable.
At first, I thought adding code formatting would be easy. There are several off-the-shelf libraries for code formatting. Then I noticed that the library I was using did not preserve the cursor position when a textarea was formatted. The cursor would automatically be pushed back to the end of the textarea input field. This was a significant user interface problem.
After a lot of thought, I came up with a solution.
First, take the string in the textarea before the user’s cursor as it was immediately before formatting. It is essential to look at the string before the user’s cursor in the unformatted text area. Then, take the string in the textarea after formatting.
Create a loop that has two increment variables (unformatted_i and formatted_i) that are integers set at zero. In each loop, check if the characters at position unformatted_i and formatted_i are the same. If they are, increment both variables and go to the next loop. If formatted_i is a whitespace, tab, or new line character, increment formatted_i and go to the next loop iteration.
The structure of this logic is essentially pairwise comparison but where whitespace is ignored.
If at any point unformatted_i and formatted_i do not match, or if unformatted_i is equal to the length of the unformatted string, break the loop. The formatted_i value will be the position in your formatted string to place your cursor.
With this approach, I was able to keep code formatting and also ensure that the user’s cursor doesn’t jump to the end of the file whenever the editor triggers the formatting.
What’s next
I have had fun authoring HTML documents with this interface. Here is an example of one I authored recently:
With the autosuggestion feature, I could add elements without having to type pointy brackets. I could start typing to see suggestions for the elements that started with the characters I typed, and click to accept the suggestion.
This is very much an experiment. To be more durable and easier to extend with more CSS features, I may need to rewrite the internals to use a DOM-based parser to better understand the document. This would be more resilient than manual logic to move between different elements. There are several bugs in adding text at the right place that are tricky to solve.
With that said, what I have right now works (mostly). I can author HTML and CSS on my mobile device faster than I could before. I can preview my changes. I can copy my code to save it for later. I can have fun with making web pages.
As it currently stands, this project is written in a single HTML file. This means that it is easy to copy-and-paste the editor onto your own website so you can run it yourself. This has me thinking about what other single-file utilities could be made that people could collect and customise on their sites for their own use.
You can try out the tool today in my playground.
[1] I would later add a fourth button that lets you switch between adding an element and adding a property (i.e. a class or an ID) to an existing element.