Making my Case for “One Attribute, One Line” in HTML

Imagine: It’s a beautiful, sunny Friday afternoon. You’ve just finished another feature and moved its ticket to the “Done” column.
You figure 3PM is a little early to go home, so you grab another one of those silky-smooth cappuccinos they have wherever it is you work, and decide that the next ticket you want to tackle is your web app’s authentication.

You start out with a simple input
element. You might slap a class or two on there for the sake of aesthetics, chuck a label
in front of it (something about accessibility), and you’re on your way.
All is fine and dandy. As you continue implementing your web app’s authentication, you’re tacking on attributes left and right: some banana-in-a-box two-way data-binding here, a dash of structural directives there, and heck, let’s toss in some conditional classes smack dab in the middle while we’re at it.
“Nice,” you tell yourself, “and only two lines of code!” you pat yourself on the back and go to bed feeling pleased with yourself.
Monday morning. You arrive at work after a lovely weekend camping in the woods with the partner and kids.
You dive back into your project, and while refactoring (go you!), you find that the name of onInputChange
, one of the functions in your TypeScript file, is a tad too broad. You have multiple input
fields, after all. How will you know which input field’s contents changed?
So you decide to change its name to onUsernameInputChange
in your TypeScript file.
“Nice,” you tell yourself, “this is so much clearer now!” you pat yourself on the back.
But wait! Now there’s a BIG FAT RED ERROR in the console!

PANIC! FIRE! SAVE WOMEN AND CHILDREN FIRST!
“Oh, right, never mind, ” you tell yourself, “I simply forgot to update the event binding in my HTML template!”
So you open your HTML file and find this abomination:
<label for="username-input" class="username-input-label"></label>
<input [(ngModel)]="username" (change)="onInputChange($event)" *ngIf="!loggedIn" class="username-input" [class.full-width]="isUsernameInputFullWidth" [class.is-hidden]="isUsernameInputHidden" id="username-input" name="username">
After about 5 whole seconds of trudging through this mess, you find yourself wishing there was a better, cleaner, more structured, [insert more positive comparatives here] way of writing HTML so you wouldn’t spontaneously burst into tears every time you opened an HTML file.
But what if I told you there is?
Introducing One Attribute, One Line (patent pending).
<label
class="username-input-label"
for="username-input">
</label>
<input
class="username-input"
id="username-input"
name="username"
*ngIf="!loggedIn"
(change)="onInputChange($event)"
[class.full-width]="isUsernameInputFullWidth"
[class.is-hidden]="isUsernameInputHidden"
[(ngModel)]="username">
All of your element’s attributes grouped by type,
- regular attributes
- (directives)
- structural directives
- event bindings
- property bindings
- two-way data bindings
and then sorted alphabetically, each attribute on its own line.
“Oh no! More lines of code!”, I hear you think. “I was told more lines of code is bad! And more scrolling! My poor scrolling finger!”
Yes. More lines. Thirteen whole lines, to be precise. Big whoop! Number of lines doesn’t matter. This isn’t Assembly. This is HTML. SLOC is irrelevant.
And not only that: having every attribute on a separate line makes Git diffs so much clearer as well. But don’t just take my word for it, judge for yourself:

versus

Just look at it! It’s so pretty! It’s so convenient! It’s everything you ever wanted! And more!
So here’s my case for One Attribute, One Line. Make of it what you will. I can’t force you to do anything. But please, next time you decide to crumple up an HTML tag into a single line of over 140 characters, think of your colleagues. Their time is just as valuable as yours (or probably even more so, you one-line monster).