The code for copy-pasting may be updated periodically as I learn and build upon them. You need at least some familiarity with the Twine editor and HTML/CSS to understand how to customize the existing template. It is intended for intermediate-level coder who is interested in trying to emulate the look-and-feel of ChoiceScript using Twine.

Things to keep in mind when using the Twine editor...
  • It may crash sometimes without warning, so remember to "Publish to File" often to save a "hard-copy" of your project.
  • Don't use the "Search & Replace" in the editor. It's imprecise and may break code. And because there's no undo when you close out of a passage, this breakage is not easy to reverse. Just "searching" is okay. If you really want to do search and replace, do it outside of the editor code, the copy-paste back into the passages.
  • When you start the editor (on your computer or on the web version), double-check that you are using the SugarCube story format.
What is that ":: passage title..." notation in the .tw file? Twee 3 notation
  • :: Header [nobr] {"position":"105,105"}
    In the above example, the passage title is "Header", and it has one tag named "nobr". In the Twine editor, its position is on 105 px along the horizontal axis, and 105 px along the vertical axis.
    :: textboxplus [nobr widget]
    In this example, the passage title is "textboxplus", and it has two tags named "nobr" and "widget". Both of these tags are SugarCube keywords so they must be spelled exactly like this.
    :: Start [noheader nofooter]
    In this example, the passage title is "Start", and it has two custom tags named "noheader" and "nofooter". You can rename them if you understand what the relevant code is doing using this information. If not, leave them as is.

If you only use the Twine editor, once you extract from the zip file...

  • the contents of the .js file can be copy-pasted into the Story Javascript section of the project.
  • the contents of the .css file can be copy-pasted into the Story Stylesheet section of the project.
  • Inside the .tw text-based file, copy contents under Twee passage headings and paste into manually-created passages in the Twine editor, but don't include the Twee notations in the passage titles (see Twee 3 notation to distinguish between Twee notation and actual code).

The template code attempts to emulate ChoiceScript for:


The formula of fairmath can be found on the ChoiceOfGames website. Here I modified someone's macro so that it can handle both addition and subtraction.

<<fairmath "$bold" -5>>
This is an example of how it is used in the passage.
Macro.add('fairmath', { ... });
Inside the .js file, you can search for "fairmath" to see how it is defined in this context.

Stats screen

Any passage can take on the role of a "stat screen," and it doesn't have to be named that way specifically like in ChoiceScript. The simplest way to do it is just by linking to such passage as you would any other passage, then use the undo option to return to the game. But if you have multiple sub-sections inside the stats passage, navigating between them and then undo all the way back to the game can become a hassle.

So I use Chapel's Dialog API macros to open up a dialog/popup panel that shows me the content of the stats passage.

<<button "Stats">><<popup 'Stats' "Stats">><</button>>
<<link "Stats">><<popup 'Stats' "Stats">><</link>>
See documentation for explanation of what individual parameters of the popup macro is referring to.

The immediate advantage of this method is that it will not need the engine to remember which passage was visited before. When you close it that dialog panel, you will be back to the game.

If you are interested in making subsections inside this kind of Stat screen, search for "CSS responsive tabbed navigation" online to find some inspiration on how to code it in that passage. I didn't include mine here yet because I fear it's already getting too complicated for relatively new SugarCube users.

Opposed pair / Percent bar

<<opposed_stat $kind "Kind" "Cold">>
<<percent_stat $energy "Energy">>
Example usage in the passage.
<<widget "opposed_stat">> ...  <</widget>>
<<widget "percent_stat">> ... <</widget>>
Search for "opposed_stat" or "percent_stat" in the .tw file to see the widget code.
.statBar { ... }
.percentBar { ... }
Search for "statBar" or "percentBar" in the .css file to see the stylesheet code.

Achievement notification

I have not yet made a complete replica of the achievement system in SugarCube, because I haven't decided how I want mine to work in my project, so keeping it relatively bare-bones. SugarCube documentation recommends the State.metadata approach to handle both achievements and new-game-plus, but it also recognizes that this method breaks in private browser mode or if user has restrictive cookie settings. If I recall correctly, achievements in ChoiceScript persist between saves. But here because State.metadata is not stored in game state or save file, a loaded game will not remember the achievements. Hence my temporary decision to use flag variables instead.

<<achieve "storyend">>
Example usage in the passage where it brings up the notification.
<<if flag("a_storyend")>><<achievement "storyend">><<else>>???<</if>>
Example usage in the passage where all achievements are displayed to the reader.
<<widget "achieve">> ... <</widget>>
<<widget "achievement">> ... <</widget>>
Search for "achieve" inside the .tw file to see the widget(s) code.
setup.achievementstext = {
   "haggled" : "You haggled for a higher pay.",
   "storyend" : "You made it to the end of the story."
setup.getAchievement = function (name) {
   return setup.achievementstext[name];
Search for "achieve" inside the .js file to see where the achievement text are defined. Because the achievement text may be very long, I don't want to save the full text inside a variable that is saved when navigating from passage to passage.

It has more features than default ChoiceScript:

built-in undo/redo navigation

By default, SugarCube tries to save a copy of all global and relevant temporary variables every time the game goes from one passage to another. It can save up to 100 states of them by default (as in if you have seen 100 passages, you can undo 99 times), which can be changed with the Config.history.maxStates variable.

This amount of state-saving can slow down the game if you keep a lot of global variables in the game, of if those variables contain a large amount of information (like a long string of text). So it's good to get into a habit of only using minimum amount of global variables (that must be remembered by the story), and let unchanging text or macros/functions be kept as hard-coded Javascript code, and keep them in the "setup" object.

ChoiceScript made a stylistic choice to not include the undo option, but I wanted to have at least one undo in case of mis-click, so I put the below code in the .js file to have 1 level of undo:

Config.history.maxStates = 2;
If you want to simulate ChoiceScript and not allow undo, set this variable to 1 instead of 0. This will also automatically remove the navigation arrows in the left panel (if you keep that interface).

built-in save system

Related to the state-saving feature in SugarCube, a natural extension is a save system. And the best part is it allows the saved content to be outputed to the hard drive and not stay inside the browser's localStorage (which can be cleared out immediately after closing the browser or periodically to save space).

By default it has 8 save slots (saved to the browser's localStorage, if available). There is one autosave slot that will appear in the Saves screen if you enable it in the Config.saves.autosave variable.

Config.saves.autosave = ["autosave"];
In the above example, any passage with the tag "autosave" will cause the engine to save into the autosave slot. I have been doing that for the starting passage of my chapters. You can change that tag name to anything else (other than SugarCube keywords like "nobr", "widget", "script", "style", etc.).

One reason ChoiceScript doesn't have save system by default is because the limitation of not being able to dynamically create new global variables on the fly means every modification or addition to the number of global variable makes an old save useless, thus forcing player to restart the game anyway. And since new ChoiceScript coders are not skilled at knowing exactly how many global variables to use from the start, having a save system just causes disappointment of readers in the early stages of development. Your mileage might vary.
In SugarCube, you can dynamically create global variables (ex. $choseOptionB) any time - not recommended to do it just because, but it's doable. And you can "unset" a variable to remove it from history. So at least that CS concern is not as sharply felt here.

ChapelR's file system, to export variable information to a file

This can be used to transfer data between games. I haven't used it extensively because I'm still writing the first story, but a quick test shows that I can export State.variables to a file and import it back into the game to use, like a very basic save-to-disk method that SugarCube has (the engine's built-in save system saves more information, so this does not fully replace the existing method).

(You can try to import a saved-to-disk file to a sequel, as long as both games share the same IFID number and StoryTitle content, which probably doesn't make sense.)

Other misc. features:

ease of interface modification

There is a reason ChoiceScript is so limiting. It removes choice paralysis on the part of a beginner. It works out-of-the-box for mobile devices, so the user can focus on writing the story content instead. But what if you want to change how the user interface looks? When I first begin making this template, I modified the existing structure (with very messy code), but recently I decided to try using the StoryInterface special passage, which is meant to allow users to build the structure from scratch (less messy code).

Being able to change how this web page looks (every Twine game is an HTML file, same for ChoiceScript's output file, so both are technically fancy web pages) means you can change how the choices are displayed to the reader. Instead of ChoiceScript's one-question-at-a-time (it's possible to do multiple, but no dropdown menu) style, you could show readers a combination of textboxes and dropdown menu to make multiple choice selections at once. So I tried it to make the character generation passage.

Now you can also implement your own color themes and font choices. See sample code in the documentation. In the .js file, search for "theme" to see the Javascript code, and tweak as necessary.

Setting.addList("theme", { ... });

ChapelR's pronoun template

This set of Javascript code (included in the template) only deals with the main character, but it handles the she/he/they display very elegantly, and if you want to allow the reader to change the pronoun at any time in the game, you can. By default the code has a variable "showSetting" set to true. If you don't want that option, set it to false. I have done so in the current iteration of the template.

"This is $name, ?they <<verb "are">> here to learn a trick."
"This is $name, ?she <<verb "are">> here to learn a trick."
If the gender has been set, either through the configuration options or manually by using the below code, that sentence will output the correct pronoun and verb, despite the odd looking SugarCube code.
<<run gender.setPronouns("female")>>
See more details in the documentation.

The Template system in SugarCube can be used for other text-substitutions, mainly to keep the narrative text more readable, like how multireplace in ChoiceScript attempts to keep if/else out of the narrative for a word or two (like conjugation for singular or plural pronoun) that might change depending on a variable.

HiEV's dynamic event flag system

This attempts to keep the State.variables array as small as possible (aka you don't need to define every potential flag value at the beginning of the story); for short-term remembering of flags you could also just rely on hasVisited("passage") check instead of a flag value.

Technically you can already do something like this by set a variable when you need it, then unset it to remove it from the game. But you might want to specifically separate a list of story flag variables for other purpose. Similarly, you can copy the code and tweak it for other separate array of variables, like for relationship variables, or choice-tracking variables, etc.

The key take-away is that keep any such list of variables as small as possible by removing them when they will no longer be used.


  • I'm currently not considering adding any multimedia element to my Twine game, thus I'm probably not likely to add any related code to this template
  • The reason I stopped sharing the compiled HTML is because I don't know how to make sure the template user will have a unique IFID without them creating a blank project in the Twine editor or use the Tweego compiler to generate it. To avoid potentially multiple projects using this same IFID, I won't provide the IFID in the code.
  • Contact me if you need help with the setup. :)
  • Misc. Development Notes
    • StoryAuthor passage is optional.
    • Don't change StoryTitle passage content once you decide on one, because that content is used by SugarCube as a unique identifier between projects (such as for If you change your mind about the story title for viewers, put the new title in StoryDisplayTitle to change how viewers see it. If you really want to permanently change this "id" of the story, expect all existing save files for your project to stop working.
    • If you are not sure which CSS id/class you need to target to change the styling, use browser's Inspector tool to find the element on the page. (What are browser developer tools?)
    • The code structure in "loopchattemplate" attempts to simulate a *hide_reuse scenario in ChoiceScript. If you want more lines in a self-looped conversation hub, add more choice_shown blocks with incremented "lineXsaid" flag value. If you want to branch into a second tier of self-looped conversation, I would use the same template structure, but use different series of flag values, so something like "a1said", "a2said", "b1said", etc. Whatever makes sense to you. Remember to set all of these flag values to false (essentially removing them from game state so they don't unnecessarily accumulate space in a save file) when you exit the conversation passage.
    • Twine editor and proofing formats that recognize the passage links (either like [[linktext->destination]] or [[link]]) will always draw arrows dynamically between connected passages, even when these links are hidden from view between /% and %/ comment symbols. However, the "button" and "link" SC macros, or my "choice_shown" and "choice_enabled" custom macros, don't cause the Twine editor to draw arrows. If you need arrows drawn (where they are not automatically drawn) for the purpose of visualization, use the "hidden link" method, otherwise leave them out.
    • A widget/macro can do operations in place but cannot return a value. Define a custom function when you need a return value.
    • Custom Javascript functions can be added to the game by attaching them to the window object, which exists by default in the browser environment. However, theoretically you could accidentally declare a function that already exists in the window object, and thus causing confusion or worse. The safer method of adding custom functions to the game is by attaching them to the SugarCube-provided setup object (ie. setup.myFunction = function() {...}). The minor downside of this compared to attaching to window is that you must always preface the function with setup, so HiEV's flag() function call would be setup.flag() if it was defined that way.
    • <<nobr>> macro will break some markdown syntax such as bullet points converted from asterisks. Think about how simple/clean you want the story to look in your editor of choice when deciding between nobr macro, nobr passage-tagging, or the Config.passages.nobr (ie. Config.passages.nobr = true;) setting. They each have their advantage and disadvantage, whether it's more <<nobr>> in the text, more tags to add by hand, or <br> everywhere.
    • Wherever Javascript can be run, $var1 is essentially State.variables.var1, _var1 is essentially State.temporary.var1. State.passages keeps track of all visited passages (and does not remove duplicates), but you cannot change it in the conventional way. So if you need to transfer a list of visited passages to a sequel, let's say, copy its content to something like State.variables.visitedpassages, then you can access them upon exporting State.variables via Chapel's export method.
    • A passage that is "included" in another will not be considered as visited by hasVisited(). Be aware of this quirk and either use flag value to denote the included passage visited, or don't use the include method in that case.
    • The scope of a temporary variable extends to the passage(s) that "include" the passage it is declared in.

Relevant links:
* Twine (Discord server invite on the homepage)
* SugarCube 2 documentation
* Tweego, an external compiler for Twine code
* Twine support forum
* Twine subreddit
* Demo page for ChapelR's custom macros for SugarCube 2
* ChapelR's custom macros for SugarCube 2
* HiEV's custom macros for SugarCube 2
* Choice of Games
* my video series on working in Twine SugarCube (a work-in-progress)

StatusIn development
Rated 5.0 out of 5 stars
(3 total ratings)
Made withTwine
Tagssugarcube, Project template, Twine
Code licenseMozilla Public License 2.0 (MPL)
Average sessionA few seconds
InputsKeyboard, Mouse, Touchscreen
AccessibilityColor-blind friendly, High-contrast, Blind friendly


Download 135 kB

Development log


Log in with to leave a comment.


thanks for sharing this!

Thanks! Let me know if there’s any issue in making it work for you.