Framing a portion of text

Twine Version: 2.3.9
Story Format: Sugarcube 2.31.1

If this particular question has already been answered in another thread, I apologize but I have been unable to find an answer. So, I have been working on a project for a little over a week, setting up all the resources I need for the game (characters, locations, equipment, etc). Yesterday I finally felt ready to start on the game itself but I ran into a problem rather quickly. I spent a couple of hours trying to google a solution yesterday, and all day today as well. I am trying to establish a basis for drawing a frame around specific portions of text; namely dialogues. I have tried to do so in CSS (the story stylesheet) and I can define the attributes for the frame but I don’t know how to call it, or even how to make it a frame in the story stylesheet. Some sources (CSS Forms) simply says “input” and others say .label or textarea. I really don’t know what else to do, other than start a thread here and hope someone smarter than myself has the answer.

I don’t need these “text boxes” to contain links or otherwise be possible to interact with. I simply want it to be a small frame with a separate background color and text color, to easily distinguish between general descriptions and “speech”. My original thought was something along the lines of using the /div or /span functions, like you do for colors, text alignment, bold or italic text, and so on. I found an example for drawing buttons, and that’s what I worked from at first but when I couldn’t crack it, I turned to google for help, and now I am here. I appreciate anything you can give on this subject.

I’m not quite clear on exactly what you’re looking for, but you might want to take a look at the “Speech Boxes” section of my Twine/SugarCube sample code collection. Even if it’s not exactly what you want, hopefully it will show you how to do the kind of thing you’re trying to do.

Hope that helps! :slight_smile:

The speech boxes was exactly the kind of thing I was looking for. Thank you very much :slightly_smiling_face: Quick question, though: In the example for speech boxes, it refers to class. Is that JS classes or non-generic object types?

To create a class you open your story stylesheet and write it with an initial dot.


.protagonist1{text-align: center;
  font-size: 20px;
  color: yellow;
  width: 600px;
  position: relative;
  margin: 0 auto;
  border: solid;
  border-width: 2px;
  border-color: yellow;

This class centers a text, within yellow border and somewhat larger letters.

In any passage you can call any class with either a div or a span element, though span should ignore some instructions as it only affects characters, while div affects paragraphs.


<div class = "protagonist1">Speech from protagonist1</div>

Neither. It’s referring to CSS (Cascading Style Sheets) classes.

In CSS you usually refer to HTML elements by a combination of three “CSS selectors” (from most general to most specific): type, class, and id. For example, in this HTML:

<span id="idname" class="classname1 classname2">...</span>

the type is span, the classes it has are classname1 and classname2, and the id is idname.

In your CSS code, found in the Stylesheet section, you’d refer to them as span, .classname1, .classname2, and #idname, like this:

span {  /* This makes the text color in all spans default to 'white'. */
	color: white;
.classname1 {  /* This makes elements with this class default to 'italic' text. */
	font-style: italic;
span.classname2 {  /* This makes only span elements with the class 'classname2' default to this background color. */
	background-color: #333;
#idname {  /* This makes the element with this id default to 'bold' text. */
	font-weight: bold;

Note that IDs should be unique on a page and have a “#” added in front of their names, while multiple HTML elements can share the same CSS class or classes and they have a “.” added in front of their names. Note that span.classname2 combines both the type and class so that it only refers to <span> elements that have the “classname2” class.

If two different CSS stylings contradict each other in some parts of your HTML page, then the one with the greater specificity wins. If they have equal specificity, then the last one listed wins. This is the “cascading” part of Cascading Style Sheets. (Note: You can also add !important to increase the specificity, e.g. color: white !important;.)

See here for a full list of CSS selectors and here for more general CSS info.

Hope that helps! :slight_smile:

If I understand this correctly, I have to create each individual npc in my game as a separate class in CSS then?

The attributes I assign to a class, are they even important, seeing as I will only be using said classes in speech boxes? Oh tiny bonus question, can I still use story variables in the CSS stylesheet? Say, the main character’s father, for example, he has the same last name (which the player can customize) as the main character but can I use that name variable in the class?

Indeed, if you need all your NPC to get a visual style you need a class by NPC. However you might want some secondary NPC to get a generic visual style, allowing them to use the same generic class. So you can have classes names for individuals like .father or .diana, or somme classes names very generic like .women or .teachers.

Only if they would have different styling than each other. There are other ways besides that, but generally this is the least tedious method to use.

The CSS classes important if you want them to actually be used. If you don’t add the classes to the HTML elements and then style those classes in your Stylesheet section, then all you’ll get are standard HTML elements and text.

Am I missing something in your question?

No, you currently cannot use story variables in your CSS (though the author of SugarCube is currently contemplating about whether or not to add that feature to SugarCube, see here).

Rather than do that, you can simply use story variables to either determine which class is used or what styling is used. For example:

<span @class="$Bob.class">Bob text here.</span>

 - or -

<<set _color = $Bob.color>>
<span @style="'color: ' + _color + ';'">Bob text here.</span>

The @ in front of the attribute name tells SugarCube to set the attribute to the result produced by the contents of that attribute, which allows you to use variables and functions when setting them. (Note: The above examples assume that $Bob is a generic object with “class” and “color” properties.)

Hope that helps! :slight_smile:

Thanks a bunch, both of you, this works wonderfully. I really appreciate the help. I have one final question, though, regarding the speech boxes. Is there a way to adjust the space between the ID (name of the speaker) and the contents of the box (the actual dialogue)? It keeps a very wide gap, so you don’t need many he said/she said in a passage before it becomes incredibly sizeable. If there’s no easy answer, then I can certainly use it as is but I would appreciate any tips :innocent:

It’s just HTML and CSS. You can make it look however you want.

I’d recommend using the Developer Tools window in Firefox to play around with the CSS until you get it how you want, since Firefox tracks any changed CSS with a green line to the left of any modified CSS. Just open a page which shows the speech box in Firefox, right-click on the speech box, and then choose “Inspect” to open the Developer Tools window. After that, just select the HTML element that you want to style in the “Inspector” pane, and then you can play around with its CSS in the “Style” pane on the right. (You may need to click the “…” button and pick “Dock to Bottom” if your Developer Tools window is docked on the side of the screen, instead of the bottom.)

In that “Style” pane you can add, modify, or delete CSS styles of the HTML element you have selected on the left, until you get everything looking the way that you want. Then just use the green line indicators I mentioned above to find your changes, and copy those changes to the CSS in your Stylesheet section. Once you’ve done that, then it should look the way that you want.

When I’m styling things on a web page, I tend to spend a lot of time in the Developers Tools window, since it shows you immediately what those changes will look like, so you can make sure everything looks good. It’s also fun learning all of the different CSS styling stuff you can do from there. (Hint: CTRL+SPACEBAR will often show you a list of options when editing there.)

Spacing around things is usually handled by the “margin” and “padding” attributes, so I’d recommend starting there.

Have fun! :slight_smile:

1 Like

Thank you very much. I guess I will be installing Firefox again, then :wink: This has been a really big help, I could never have cracked this nut on my own, so thank you.

Okay, I have to ask for a little more assistance. I have the speech boxes working just the way I want, and looking good too. Now that the function appeared to work as it should, I finally got around to prepping a few images as avatars but they don’t show up like they should. There’s just an empty field, exactly like before I assigned the images. I have added the location in the JS (as per your guide) and I have named them as URLs in the CSS code (as per your guide). I have tried moving them to different folders and renaming the locations in twine. I have tried running it through twine and through a published version but no matter what I do, I can’t get the avatars to show… Any advice? I have added the JS below, as well as a single class (and associated avatar):

Macro.add('speech', {
	tags     : null,
	handler  : function () {
		var id = this.args[0], name = id;
		if (this.args.length > 1) name = this.args[1];
		var output = '<div class="speech ' + id + '">';
		output += '<span class="avatar"></span>';
		output += name + '<hr>' + this.payload[0].contents + '</div>';

/* Strictly for Twine testing */
if (document.location.href.toLowerCase().includes("/temp/") || document.location.href.toLowerCase().includes("/private/") ||, "storyFormat")) {
	/* Detect if you're running in Twine. */
	$(document).one(":passagerender", function (ev) {
		setTimeout(function () {  /* Delay needed so CSS values are set. */
			$(".avatar").each(function () {
				var url = $(this).css("background-image");
				url = url.replace(/\"/gi, "").replace(')','');
				url = url.slice(url.lastIndexOf("/") + 1);
				$(this).css("background-image", "url('file:///C:/OY/Img/" + url + "')");
/* CSS Speech Box */
.speech {
	color: Black;
    background-color: silver;
    border: 3px solid springgreen;
	border-radius: 5px;
	padding: 6px 6px 6px 6px;
	box-shadow: 5px 5px 3px Black;
.avatar {
	display: block;
	padding: 1px;
	height: 84px;
	width: 84px;
	float: left;
	margin: 0px 10px 0px 0px;
	border: 2px solid springgreen;
	border-radius: 5px;
	background-size: contain;
	background-repeat: no-repeat;
	background-position: center;
/* CSS NPC Class */
.dean {
	background-color: silver;
.dean .avatar {
	background-image: URL("images/dean_avatar.jpg");

Hard to say without seeing exactly what you’re doing, but if the image isn’t in the C:\OY\Img\images directory (which I suspect it’s not) then it won’t work. (If I’m reading your code correctly, then file:///C:/OY/Img/ + images/dean_avatar.jpg = file:///C:/OY/Img/images/dean_avatar.jpg)

I’d recommend using the Developer Tools window again and look in the Console section to see the error message for where it fails to find the image to see exactly where you have it looking for the image, and then fix that.

If that doesn’t fix it, then post a link to a ZIP file showing a simple example of what you’re trying so that we can take a look at it.

Hope that helps! :slight_smile:

No, you’re right, it’s not located in Img/images but every time I tried a change that didn’t work, I reverted the code back to where I started. The image I want is located in C:/OY/Img/Avatars but I have tried that too: Both changing the JS to file:///C:/OY/Img/ and changing the CSS to Avatars/dean_avatar.jpg. I have tried different combinations of including more or less of the actual adress in CSS or JS, moving the picture to a single subfolder (just /Img instead of /Img/Avatars) and change to code to fit but it doesn’t work. Running it in Firefox this morning, and using the Inspector as you suggested, I couldn’t even see that it was supposed to use an image. There’s not a single mention in the code of a background image for the avatar - it’s just described as a box.

I would love to post a link for a zip, but where do I upload the compressed file to? Is there a good place for temporary files like this?

Update: Nevermind, I figured it out. As I continued my work on another avatar (which worked right away), I realized that it was human error. I forgot to close an argument with a curley bracket which made all the following CSS classes invalid. Thanks again for all your help, I couldn’t have done it without your assistance, and thank you for being so patient with me :blush:

No problem, glad I could help.

Also, for temporary uploads where I need to share files across the Internet, I find that the SendGB site works quite nicely. Just make sure that you click the little “link” icon first, since it defaults to using email instead. Once it’s uploaded there, then you can just post the download link that they give you, and that link will automatically expire after whatever time limit you give it.

Have fun! :slight_smile: