Harlowe: thinking outside the (box:)

▲ Return to the Table of Contents

Custom HTML & CSS

  • Taking control of the look and structure of your story early on can save a lot of potential reworking.
  • Using CSS directly in your story’s Style Sheet allows you to more easily leverage the power of CSS with responsive designs for mobile, tablet or desktop.
  • Custom HTML is more meaningful to your story and much easier to read, understand and maintain.

Reasoning :

I like readable, understandable code. I was drawn to Harlowe, not because it’s the default story format, but because I was really impressed with the syntax highlighting in the Twine editor and the sophisticated language Leon Arnott (Harlowe’s programmer) has built. However, I felt that the code to style the passage elements began to blend with the story logic code. It all looked the same, until I started using HTML and the editor gave it a distinct colour with the code syntax highlighting. It’s also just solid practice to use CSS and HTML for any web design, which is the core of Twine stories.


Custom HTML

If you were to use regular HTML to style your story elements, it might look like this:

<div class="header"> Bartholomew and the <span class="slime">Oobleck</span> </div>

…but it feels a little clunky. I then made my own <x-x> tag with custom attributes of header and slime, and it just looks cleaner:

<x-x header> Bartholomew and the <x-x slime>Oobleck</x-x>  </x-x>

Note: To conform to HTML standards, custom tags should have a hyphen in them. Just having an <x> will work in today’s browsers though (even easier to read, in my opinion), but I don’t mind the <x-x> look.

HTML fully supports custom tags and attributes. It’s just not a very common practice because of a fear that Google may not crawl and read your web site properly if it encounters tags it doesn’t understand. Fortunately, this is not a concern with Twine stories. <x-x> may not mean anything specific, but neither does <div>; you still have to dive into the CSS to understand what’s going on.


Fun Fact: Harlowe stories are generated with custom HTML tags and attributes: <tw-story>, <tw-passage>, <tw-link>, <tw-hook> and the list goes on and on… and all receive custom CSS styling without any issues.


CSS For Custom HTML

You don’t need to make any special concessions for CSS to style custom HTML tags and attributes. With the regular <div> and <span> method above, your code in the story’s Style Sheet would look like:

.header { font-size: 2rem; }
.slime { color: green; }

…with custom HTML, it would look like:

x-x[header] { font-size: 2rem; }
x-x[slime] { color: green; }

Note: I keep the custom x-x as a part of the selector in CSS in case any of my custom attributes match the name of an existing attribute for a standard or possible future HTML element. (I only want my specific tags to be styled.) I also try to use one-word attribute names for readability, but you can always use slime-colour or any other multi-word tag or attribute name.


Tip: You can nest attributes within the same tag if you want. For example, if you wanted the whole title to be green just for this one case, you could use <x-x header slime>. This strategy gives you a lot of flexibility with combining and reusing styles. Coincidentally, you can also do this with conventional HTML class names using <div class="header slime">.


Bringin’ It Together :

Always try and let CSS do as much heavy lifting as possible because it’ll keep your passage code more manageable and maintainable down the road. In the example below, I’ve added a horizontal borderline below the header text within the header’s own CSS style. I can change the style of the header in almost any capacity without having to go back and change any passage code throughout the story using that style. There is so much that CSS can do with elements that it’s not really necessary to have multiple HTML tags to construct complex styles.

The story’s Style Sheet in this example:

:root {
        font-size: 24px;   /* <== base font size for the entire story */
        --header: #777; /* <== if the r, g or b values are matched (00) then one number (0) will suffice */
        --line: #555; /* <== changing this value will change all styles using this variable */
        --decision_background_hover: #222;
}
x-x { display: inline-block; }   /* <== (!) default display type */
x-x[header] {
        display: block; padding-bottom: 2.5rem;   /* <== add a bit of space above the underline border */
        border-bottom: 2px solid var(--line); 
        text-align: center; font-size: 1.5rem; color: var(--header);
}
x-x[narration] {
        display: block; width: 100%; padding: 0 1rem;   /* <== horizontal padding to let the header underline be wider than narration text */
}
x-x[decision] {
        display: block; padding: 2rem 0; width: 100%; /* <== allows child elements to use % properly */
        border-top: 2px solid var(--line);
        border-bottom: 2px solid var(--line);
}
x-x[decision] > tw-expression > tw-link { display: inline-block; padding: 0.6rem 1rem; width: 100%; }
x-x[decision] > tw-expression > tw-link:hover { background-color: var(--decision_background_hover); }
/* ^^^ added subtle background to make the choice feel more clickable ^^^ */

…we are even identifying nested Harlowe elements within the CSS ( x-x[decision] > tw-expression > tw-link ) which allows our passage code to remain even more readable and concise:

<x-x header> \
The Sleep Inn
</x-x>

<x-x narration> \
You slowly come to, as a stout dwarf drags you out the door of the tavern. "Buy a bar, she said. Follow your dreams..." he grunts with disgust.
</x-x>

<x-x decision> \
[[ Resist him, insisting that you aren't done drinking yet. ->Resist Passage]]
[[ Yell out that you're being oppressed. ->Yell Passage]]
</x-x>

Note: I’ll explain a good practice for identifying and styling specific Harlowe elements in another post.

The approach of custom HTML and CSS is unconventional, but it does have its advantages. If you like what you see in the examples above, then it’s worth exploring for your own Harlowe stories. It’s also an independent and transferable method of styling your Twine stories as it doesn’t rely on specific story format code.


Rationale :

  • HTML and CSS is the most performant way to style and render web page elements.
  • With everything being in your story’s Style Sheet, it’s very easy to reuse and share Harlowe story templates.
  • You can repurpose code from other websites easier because you don’t have to refactor it with Harlowe syntax.

▲ Return to the Table of Contents