Heading

This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
min read

Toggle basic text formatting improved

Word • Macros • Editing • Formatting
Peter Ronhovde
23
min read

We create a macro to toggle the formatting of either the initial selection or the current word but without needing to select the word. The macro can be trivially extended to other character formatting or larger blocks of text like sentences or paragraphs.

Thanks for your interest

This content is part of a paid plan.

Toggle basic text formatting

It's surprising how we do some tasks thousands of times without realizing or even considering they can be done more efficiently … if we only do a little up front work.

This macro applies italics to the current word without needing to select it. If an initial selection exists, the macro instead applies italics to the selection like normal which maintains the expected behavior (equivalent to typing Command+I on Mac or Control+I in Windows). The macro can be trivially extended to sentences or paragraphs to to indicate internal dialog in a novel.

Example of quick format toggle shortcut where no selection is necessary
Quickly toggle italics where no initial selection is necessary

Why bother?

We all get in a rut of just doing things the way we already know. It already works, right?

Well, yeah, I suppose it works … but repetition is annoying, and we could also mow our lawn with scissors. That works too. And what's a computer? Oh, that thing over there? Why give up on good-ole quill and parchment? It worked just fine for centuries.

The trick is to enhance the process while only disrupting our workflow for the better. With that said, we might need to overcome a little muscle memory, but we emerge with a faster process for editing our documents.

Create the empty macro

Open the VBA editor and create the following empty macro. If you prefer to use the Word interface, a previous post covers that process.

Sub ToggleWordItalics()
' Italicize the selection or the current word without needing to select it

End Sub

The single quote tells VBA the rest of the text on that line is a comment meant for human readers. We start our macro steps on the empty line.

A sibling macro ToggleWordBold formatting would be a natural variation, but the italics version is more useful in practice when writing or editing a novel. I don’t use specific character formatting much beyond italics and bold, so I omit other character formatting variations. For anything more complicated, I usually use styles.

What is an object in VBA?

In order to work with a document, VBA must somehow internally represent the various elements. Examples of such virtual (in the everyday sense) "objects" include the Application, a Paragraph, or a Document. More practical types exist such as a general document Range. Each object type includes various actions (called "methods") and data (called "properties") that allow us to manipulate them or any associated document content. Most VBA objects are named as expected only capitalized.

These objects act like composite data types for our document elements where we can declare variables to represent and manipulate specific elements in a document. Most object methods or properties are reminiscent of what we can do with the real-life element in a document.

What is the Selection?

Every document has an on-screen selection or insertion point waiting for us to do something. VBA represents this concept with an object called the Selection (with a capital S). It also represents an insertion point (i.e., the blinking I-bar waiting for text) in the document since an insertion point is essentially an empty selection. While non-contiguous selections with multiple separate selections can be used in Word, only one Selection object exists per document.

Without getting into too much detail at the moment, the Selection spans a certain amount of document content marked by Start and End positions, but it also includes many other methods and properties that allow us to control its position, change various settings, and handle other details specific to its purpose. See a separate article for more information about the Selection.

What is a Range?

Some document elements like words or sentences are not full VBA object data types on their own. Rather, they're represented as a range of document content. As such, VBA needs a general object to handle such elements.

A Range corresponds to a general span of text in the document with defined Start and End positions similar to the Selection. We can also assign document ranges to a variable and treat it much like an invisible selection or insertion point. The various methods or properties allow us to move it around the document, change its extent, delete or modify the spanned text, etc. A separate article presents a brief introduction to Ranges.

What is a word?

In language, we think of words as conceptual units, but in VBA, a word is just a document range. While it's not a distinct object data type, we can nevertheless manipulate it using the regular range methods or properties.

Use a Range variable in this macro

Since a word is a document range, it is natural to use a Range variable to represent it in our macro. Moreover, if we use a Range variable from the beginning, the macro won’t disturb the starting position or selection when it runs. We can toggle the formatting of the current word and just keep working.

What are the manual steps?

If a selection exists when the macro is run, we just toggle italics (like using Command+I or Control+I in Windows). Not much else to do there. It doesn't even need a macro, but we're trying to include the basic functionality, so our macro feels intuitive in reasonable use cases.

Assuming no initial selection exists, what steps would we use to manually italicize the current word?

  1. Move to the beginning of the current word (Command+ or Control+Left arrow)
  2. Extend the selection to the right over the word (Command+ or Control+Shift+Right arrow)
  3. Tap Command+I to italicize the word

Of course, the fastest method uses standard Word keyboard shortcuts to move between words and select the text (swapping out the Command key on a Mac for the Control key in Word for Windows). We could double click the word instead before applying the character formatting, but using the mouse is often slower than it feels.

The above steps aren’t horrendous, but we're converting it into a single keystroke while maintaining the expected standard formatting behavior for a selection.

How will the macro work?

The main goal is to italicize the current word, but a second goal is to keep it as intuitive as possible. With those goals in mind, we will implement the macro as follows:

  • If the macro starts with a selection already spanning some text, we assume the writer selected that text on purpose and apply or remove italics as normal. The macro acts as anyone would expect even if they have almost know knowledge of what the macro does otherwise.
  • If no text is selected, we assume the writer wants to apply italics to the current word.

In this macro, the VBA steps do not naively follow the manual steps because we're taking advantage of how VBA internally stores and manipulates the various document elements.

Toggle italics for the current word

The main subtask is detecting an initial selection and setting the correct range before applying italics to the result.

Declare a working range

We should tell VBA we're working with a range variable. While regular VBA doesn't require it, it will eventually save us time and trouble tracking down silly, easily avoided errors.

' Declare a working range for the word or the initial selection
Dim rWord As Range

Dim is a VBA keyword to declare most variable types, and "As Range" tells VBA what the stored data type will be. I generally precede range variables with a "r" as a reminder, but VBA does not require it as long as we don't use one of it's reserved words. Valid variable names use only alphanumeric characters or an underscore to simulate a space in the name.

While the variable is named rWord based on the main use case, the writer could select more than one word, and the macro will still work as expected.

Assign the working range based on the initial selection

In terms of VBA, we identify the range as follows:

  • Text is already selected in the document → Do not change the selection and store the starting Selection range in our working range variable (although we may trim a trailing space afterward)
  • No document content is selected → Set the working range to span the current word

With this in mind, we need to make a decision based on the initial selection.

How do we detect a selection?

A rough conditional statement for our selection test would be something like:

' Conditional statement to define a different working range based on whether
' an initial selection exists or not
If a selection exists Then
'Initial selection exists, so set the working range to the current selection
Otherwise
' No selection exists, so set the working range to the current word
End the selection check

' Apply italics to whatever the range is set above ...

Unfortunately, we have a practical problem based on how VBA interprets different types of selections.

Detect the Selection Type?

We can test the general type of selection with the Selection’s Type property. This obviously includes our main interest of whether a starting selection simply exists or not, but the details are a little troublesome. For our purposes with novel editing macros, the two most important Selection Type constants are:

  • wdSelectionNormal is a regular document selection.
  • wdSelectionIP corresponds to “no selection” in the intuitive sense when no text or other objects (e.g., shape, picture, etc.) are selected in the document. The cursor is an insertion point waiting for us to type something.

Specifically, the Type property is stored as a number (technically, a Long data type) with one of these constant values. The other type constants relate to special case selections. A separate article describes Selection types in more detail.

Do not use the no selection type

If we peruse the constants table, another seemingly important constant is wdNoSelection, but it refers to a selection that does not exist in the document. It does not refer to no selection in the intuitive sense, so we will almost never use it unless we're checking for an unusual document error.

Typical selection type conditions

Since the Selection Type is a number, we just compare it to one of the expected type constants. A typical condition to test for a "normal" selection would be:

' Selection Type condition for a normal selection
Selection.Type = wdSelectionNormal ' But we have a problem ...

While this looks like a numeric variable assignment, VBA will interpret it as a True or False (Boolean) value when it's used in a conditional statement.

Problem with a normal selection

In principle, we would use this condition in the above conditional statement to check whether the current document selection is normal (whatever that means). We would take an appropriate action either way.

The logic sounds good and makes sense … yeah, you hear the but

Unfortunately, this obvious check for a normal selection is a tad—well, more than a tad—ambiguous for our purposes, and the Microsoft documentation is rather terse on the topic.

Why?

For one, it doesn't always refer to a text-only selection. It can include shapes in some cases or even non-contiguous selections. Word can obviously define what normal means for selections however they want, but non-contiguous selections don't seem to fit the intuitive meaning of normal. Allowing shapes in a selection doesn't fit the criteria for our editing macro since italics does not apply to them. See our introductory article on Selection types for more explanation.

An exhaustive validation to exclude unwanted cases for a novel editing macro is annoying and messier than we need or want.

What do we do then?

We flip the script and check for the opposite case.

Condition for no selection (insertion point)

We'll use the opposite validation by checking whether the document contains just an insertion point (no content is selected). The corresponding condition is:

' Condition for an insertion point with nothing selected
Selection.Type = wdSelectionIP ' This condition works better ...
Why is this better?

This reversed Selection check works more consistently for our detection logic because an insertion point is a more specific Selection Type. It can't include shapes or non-contiguous selections, for example. This is a convenient (and perhaps logically annoying) rephrasing of the above conditional statement, but it allows us to be more sure of the initial Selection Type in the document.

We then set the working range variable based on what the above conditional statement detects and toggle italics afterward in either case.

What does this mean for the conditional statement?

Flipping the conditional statement order, we check for not a selection as the main case.

If no selection exists (an insertion point) Then
' No selection exists, so set the working range to the current word
Otherwise
' A selection exists, so set the working range to the Selection range
End the selection check

' Apply italics to whatever the range is from above ...

It's logically awkward until you get used to it, but it's safer and reduces the number of gotchas that might sting us later.

Set a working range based on the initial word

If we want to store the initial word range, we start with the Words collection of the Selection.

Selection.Words ' Not done ...

As per the name, the Words collection contains a list of all words partially or fully contained within the selection. We specifically need the first word of the selection, which we get with the First property of the collection.

Selection.Words.First ' First word of Selection

Since the collection contains partially spanned words, this reference works even if we have only an insertion point in the document.

A word is a Range, so we can assign it directly to our working range variable.

Set rWord = Selection.Words.First

The remainder of the macro will use this working range variable. When we store a document range in a variable, we must use Set because it is not a regular value like a number or plain text, but the rest of the assignment still uses an equals = sign.

Set a working range based on the initial selection

When we already have an initial selection at the beginning of the macro, we store its range in our working range variable using the Selection's Range property.

Set rWord = Selection.Range

While the Selection is like a Range in that it spans document content, it is more specific than a Range. Thus, we tell VBA to isolate its spanned content and store that range in our working variable.

Selection Type conditional statement

Now, put the above condition and the two different range assignments together.

If Selection.Type = wdSelectionIP Then
' No starting selection, so set the range to the current word
Set rWord = Selection.Words.First
Else
' Set the working range to the current selection range
Set rWord = Selection.Range
End If

' Apply italics to whatever the range is set above ...

A range is assigned either way. This is important because unassigned object variables in VBA, including a Range, have a default value of Nothing until they are assigned to a valid document element. Attempting to use when not yet assigned would cause an error.

Ignore blank space

When assigning the word range above, Word will automatically include any trailing spaces after the word much like Word does with automatic selections. I prefer to remove the extra space(s), so the italics applies to the text but not the space after it.

To accomplish this subtask, we move the End position of the range back across any spaces using the MoveEndWhile method. Assuming we have a valid working range variable, we use:

rWord.MoveEndWhile ' Not done ...

We need to specify a set of plain text characters to trim using a Cset option. Many text characters can be specified inside double quotes as a plain text string (some exceptions exist). For a space, the character set is simply " ".

rWord.MoveEndWhile Cset:=" " ' Not done ...

The command can move the End position either direction in the document. Forward is the default, so we need to specify backward using the Count option.

rWord.MoveEndWhile Cset:=" ", Count:=wdBackward

A Range includes Start position and End positions defining its invisible extent in the document, so this command moves the End position backward in the document while it keeps finding spaces at the End position. The command doesn’t delete the characters from the document. It just removes them from the spanned content of the range variable.

The Count:=wdBackward option tells the command to move the End position left by as many characters as necessary toward the beginning of the document. All command options use a colon equals := symbol to store a value, and multiple options must be separated by commas.

One could argue this command should only apply to the word range case, but the current macro removes the trailing spaces from whatever range is set in the previous conditional statement. If you prefer to keep the two cases separate, this command could be moved to the top part of the If statement just after rWord is assigned.

Toggle the italics

We could force italics using the Italic property of a Range.

rWord.Italic = True ' Set italics regardless of the text formatting

True turns italics on for all text in the range and False obviously turns it off, but it's a little more complicated.

Real document text could have mixed formatting. While this is probably rare for italics, it's not inconceivable. (I've done so a few times in teaching documents to make a specific point.) More importantly for this macro, we don't know whether the initial text is italicized or not. If we instead toggle the formatting, the macro would feel more natural and be more useful in practice since we could perform either task.

Some techy details

While italics seems like it might be either on (True) or off (False) for text, the Italic property is stored as a numeric value not a True or False (Boolean) type. For example, if the range contains a mixture of regular and italicized text, then the Italic property will have a value of wdUndefined, so we have more flexibility for real text.

Normally, a Boolean variable can only store True or False values. For our macro, it is overly complicated to test whether the Italic property is True, False, or undefined and then take an appropriate action; but VBA makes it easy if we just want to toggle the formatting based on what is already applied to the text.

Toggle the formatting

Fortunately, VBA includes a special toggle value, wdToggle, which we can assign to the Italic property:

rWord.Italic = wdToggle

VBA will understand this particular value and reverse the italics of the text inside the range variable. More specifically, it doesn’t toggle italics character by character. Rather, it looks at the formatting of the first character in the Range and toggles the italics state of all the spanned text based on it. Most of the time, this action is at least reasonable.

Gotchas

Could anything go wrong with the above steps?

What if no word is nearby?

This is where it gets tricky and a little annoying.

If the insertion point is at the beginning of a paragraph beginning with spaces or similarly at the end of a paragraph surrounded by whitespace, the expansion over the nearby word may do strange things. Sometimes it works, but other times, it doesn't. The details that trigger a problem for the macro can be very specific.

One could argue at worst we italicize some spaces, but we can still implement a simple validation that catches and avoids most of the problems.

Include paragraph marks in the trim characters

One tweak is to include paragraph marks when we trim the spaces from the end of the range variable. This will catch an end-of-paragraph problem where the word range ends up spanning the paragraph mark. To fix this issue, simply add (called "concatenate") a paragraph mark character to the space character using a plus + sign.

' Trim any spaces or paragraph marks from the end of the range
rWord.MoveEndWhile Cset:=" " + vbCr, Count:=wdBackward

A paragraph mark must be a special character since the VBA editor would just insert a new line if we tapped Return. Word tabulates several such special characters in a miscellaneous Word constants table. For most common cases in this macro, including this character makes no difference, but this tweak sidesteps a gotcha.

We could include a tab character from the same table, vbTab, but it's probably superfluous in most novels.

Check for an empty range

In concert with trimming the whitespace characters above, a follow-up range validation will sidestep most problems we might encounter with this macro. More specifically, if the final range ends up empty—regardless of where it finishes in the document—we know it encountered one of these odd cases. If so, we just exit the macro immediately and never apply the italics.

Condition for an empty range

The easiest way to detect an empty range is to check whether its Start and End positions are at the same location. Since they are stored as character position values from the beginning of the document, we literally just check whether the two positions are equal.

rWord.Start = rWord.End ' Condition for an empty range
Empty range conditional statement

VBA will interpret this condition as a True or False value when used where a Boolean value is expected. Putting the condition into an If statement, we have:

If rWord.Start = rWord.End Then
' Working range is empty so just exit the macro
End If

We exit the macro with Exit Sub, but since the command is so simple, we can just condense the If statement onto one line.

If rWord.Start = rWord.End Then Exit Sub

The gotchas for this macro are not common in practice, but combined with trimming the whitespace characters, this range validation avoid most of them.

Final formatting toggle macro

Now, put all the above together, we achieve a nicer variation of our basic formatting macro.

Sub ToggleItalics()
' Toggle italics in the current selection or the current word if no
' selection exists

' Define the working range based on the initial selection
Dim rWord As Range
If Selection.Type = wdSelectionIP Then
' No starting selection, so set the range to the current word
Set rWord = Selection.Words.First
Else
' Set the working range to the current selection range
Set rWord = Selection.Range
End If

' Remove any spaces or paragraph marks at the end of the range
rWord.MoveEndWhile Cset:=" " + vbCr, Count:=wdBackward

' Check whether range is empty and exit if so
If rWord.Start = rWord.End Then Exit Sub

' Toggle italics in the selection
rWord.Italic = wdToggle
End Sub

I assigned my version of this macro to Command+Shift+I in Word for Mac (or Control+Shift+I in Windows) which is similar to the standard italics shortcut Command+I on Mac (or Control+I in Windows).

My personal version adds a few more tweaks to handle special cases that aggravate me, but the complexity to practicality ratio is too high to include here. For any more complicated formatting, I tend to use styles instead.

Improvements

How can we extend or improve the above macro?

Applying italics to other document elements

We can trivially extend the above macro to italicize a whole sentence or paragraph.

Why?

Some writers use italicized text to indicate inner dialog. Toggling italics for a whole sentence will let you streamline the task or change your mind more easily about what is internal dialog or not as you’re writing.

Applying to a sentence instead

The change is simple. Instead of referring to the Words collection of the Selection, we refer to the Sentences collection.

' Set the working range to the initial sentence
Set rSentence = Selection.Sentences.First

Like a word in VBA, a Sentence is stored and manipulated as a document Range. This line would replace the word range assignment in the first part of the If statement. Of course, also change the macro name and probably use a different shortcut.

Applying to a paragraph instead

If you prefer to italicize the current paragraph, use the Paragraphs collection instead but with a caveat.

' Set the working range to the initial paragraph range
Set rParagraph = Selection.Paragraphs.First.Range

A Paragraph is a distinct object data type in Word VBA, so referring to the First paragraph gives us a specific document Paragraph (with a capital P) rather than a Range like it did with a sentence or a word. While the Paragraph spans the obvious paragraph content, we must refer to its Range property to store its content range (necessary to access the Italic property). Again, this line would replace the word range assignment in the first part of the If statement.

Apply other basic formatting

The bold version is almost identical to the above macro. Just create a new macro with a distinct name and change the Italic property toggle on the last line to the Bold property instead.

rWord.Bold = wdToggle ' Toggle bold formatting

I assigned this version to Command+Shift+B in Word for Mac or Control+Shift+B in Windows.

Functions?

With all the common steps, these macros are begging for a function containing the common steps. It could even be generalized to handle different document elements, but alas, that is beyond the scope of this article. In this case, it's probably not used enough to justify the time creating and testing the necessary function(s).

Applying character styles

A previous macro introduced the idea of toggling styles. The focus was on paragraph styles, but the idea would extend reasonably well to character styles. The implementation is a little more complex since we must handle the toggle part ourselves, but the idea is similar.

Affiliate Links

If you're interested in using Word or another tool related to the article, check out these affiliate links. I may make a small commission if you purchase when using them, but there is no increase in cost for you, and it helps to support this site and associated content.

I've been using Microsoft for Business for commercial use (that's us writers) on one of the lower pricing tiers for years. I get to use my macros, have online storage, and don't have to worry about software updates.