├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── _docs ├── actionable-language.mdx ├── overview.mdx ├── references.mdx ├── style-and-mechanics.mdx ├── voice-and-tone.mdx └── word-list.mdx ├── components ├── Content.js ├── Footer.js ├── Header.js ├── Layout.js ├── Sidebar.js ├── Usage.jsx ├── UsageBlock.jsx └── meta.js ├── data └── config.json ├── lib ├── api.js ├── constants.js └── mdxToHtml.js ├── next.config.js ├── package.json ├── pages ├── [slug].js ├── _app.js └── index.js ├── postcss.config.js ├── public └── assets │ ├── favicon.ico │ ├── icon.svg │ └── social-preview.png ├── styles └── style.css └── tailwind.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist 3 | .next 4 | out 5 | 6 | # dependencies 7 | node_modules 8 | package-lock.json 9 | yarn.lock 10 | !/yarn.lock 11 | test/node_modules 12 | 13 | # logs & pids 14 | *.log 15 | pids 16 | 17 | # coverage 18 | .nyc_output 19 | coverage 20 | 21 | # test output 22 | test/**/out* 23 | test/**/next-env.d.ts 24 | .DS_Store 25 | 26 | # Editors 27 | **/.idea 28 | 29 | # example output 30 | examples/**/out 31 | .now -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.mdx 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Quinn Keast 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Pixel art of a pencil writing input helper text](https://www.uxlanguage.com/assets/icon.svg) 2 | 3 | # Product Language Framework 4 | 5 | A comprehensive set of UX copywriting and style guidelines to use as a reference, or to adapt for your product team’s own copywriting and style guidelines. 6 | 7 | As a standalone reference at [uxlanguage.com](https://uxlanguage.com): 8 | 9 | - Useful and semi-universal guidelines for strong UX copywriting. 10 | - Real examples that you can use as a starting point, and replace with examples from your own product. 11 | 12 | As a framework for implementing your own copywriting and style guidelines: 13 | 14 | - Uses [Next.js](https://nextjs.org/) and [Tailwind CSS](https://tailwindcss.com/) to turn markdown-based documentation into a minimal but useful static site that can be built and deployed with [Vercel](https://vercel.co), [Netlify](https://netlify.com), or other tools of choice. 15 | 16 | ## Why use this language framework? 17 | 18 | Content and copywriting guidelines are an amazing way for product teams to: 19 | 20 | - **Create consistency** across the product to make a usable, predictable, and on-brand experience. 21 | - **Empower teams** to make easier for anyone to write clear, effective, and useful content. 22 | - **Drive positive change** in language standards to create compassionate, inclusive, and respectful products. 23 | 24 | The problem is, guidelines take a lot of work to create. And, there’s a large barrier to adoption in that if they aren’t easily edited and made available to the rest of the team, they aren’t likely to be used. 25 | 26 | While there are a few wonderful examples of existing content guidelines out out on the internet, these existing references tend to belong to large companies and aren’t open-sourced for easy repurposing and adaptation by other product teams for their own use. 27 | 28 | This product language framework is a solution to these problems: it’s a complete set of useful and semi-universal guidelines for strong UX copywriting that can be customized and extended for your own product, and it’s a minimal static site that can be built, deployed, and put to use in seconds. 29 | 30 | ## Usage 31 | 32 | ### Fictional product: Foreword 33 | 34 | To provide realistic and useful examples throughout the guidelines, the language guidelines are built to support a fictional product called **Foreword**—a platform for people to ask for and share recommendations for books to read, based on shared reading history. All examples use this fictional product as a basis. 35 | 36 | ### Using the guidelines 37 | 38 | The content guidelines are written in markdown and entirely contained in the `docs` directory. Examples are included for most sections. To make this framework your own, go through the examples and update them to reflect real examples of your own product. 39 | 40 | ### Using the complete framework with static site generator 41 | 42 | #### Installation 43 | 44 | Clone the repo and run `npm install` to install project dependencies. 45 | 46 | #### Local development 47 | 48 | After completing the installation, run `npm run dev` to start the local development server. If you haven’t used Next.js before, reference the [documentation](https://nextjs.org/docs/getting-started) to learn more about routing and app structure. 49 | 50 | #### Editing markdown 51 | 52 | After making changes to a `.mdx` file in `/_docs`, you will need to reload the page to view your changes. 53 | 54 | #### Usage component 55 | 56 | To add more advanced examples for guidelines while still using markdown, this framework contains a component called `UsageBlock`. This component is a hacky way of using a fenced code block along with syntax highlighting to process the content as JSX, then also process its internal markdown. It looks like this: 57 | 58 | ```` 59 | ```usage 60 | 61 | ### Yes 62 | - Why does “Dune” deserve a re-read? 63 | 64 | 65 | ### No 66 | - Why “Dune” deserves a re-read? 67 | 68 | ``` 69 | ```` 70 | 71 | You may find this introduces extra complexity in your project. Feel free to remove the formatting and use plain markdown instead. 72 | 73 | ### Using with Notion 74 | 75 | The guidelines are also available as a [Notion template](https://www.notion.so/noukka/Product-Language-Framework-5465ba9f736a4e6eb299f9cd325eb1c3) thanks to [@noukkasigne](https://twitter.com/noukkasigne). (Note that the template isn’t synced with this framework and may not match in full.) 76 | 77 | ## Reuse for your own product 78 | 79 | This framework has an MIT license and is intended for you to take it, adapt it, and re-use it however you see fit. If you use it to create your own, please [share a link with me](mailto:hey@quinnkeast.com)! I’d love to see what others do with it. 80 | -------------------------------------------------------------------------------- /_docs/actionable-language.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Actionable language 3 | author: Quinn Keast 4 | date: 2020-02-17 5 | order: 03 6 | --- 7 | 8 | import UsageBlock from "../components/UsageBlock.jsx"; 9 | import Usage from "../components/Usage.jsx"; 10 | 11 | The content in our digital products and messages shape how our users perceive and use our platforms to get things done. Our writing must help our users understand and take action. 12 | 13 | ## Heading and subheadings 14 | 15 | Headings and subheadings in our interfaces have three goals: 16 | 17 | - Help users identify their **place**. 18 | - Help users understand how things are **organized**. 19 | - Help users predict what **actions** they can take. 20 | 21 | Some specific guidelines for writing headings and subheadings: 22 | 23 | - **Be concise.** Headings and subheadings should be scannable and use only as many words are needed to convey their meaning. 24 | - **Write in sentence case.** Sentence case makes it feel less stiff and formal. 25 | - **Don’t use punctuation** like periods or semicolons. 26 | 27 | 28 | 29 | 30 | ### Yes 31 | - Your next read 32 | 33 | 34 | 35 | 36 | ### No 37 | - Your Next Read. 38 | 39 | 40 | 41 | 42 | ### Conversational headings 43 | When we’re talking, we connect words with articles like “the,” “for,” “these,” and “an.” If we remove these from our writing, it makes our copy feel stiff and complicated. For conversational headings, use articles and write full sentences. 44 | 45 | 46 | 47 | 48 | ### Yes 49 | - Read these next 50 | 51 | 52 | 53 | 54 | ### No 55 | - Read next 56 | 57 | 58 | 59 | 60 | ### Microcopy 61 | For labels and microcopy, prioritize short and actionable content by removing articles like “the” and “an.” 62 | 63 | 64 | 65 | 66 | ### Yes 67 | - Add book 68 | 69 | 70 | 71 | 72 | ### No 73 | - Add this book 74 | 75 | 76 | 77 | 78 | ## Sentences 79 | Sentences should provide real value and next steps for our users. They help users cycle between “What’s going on?” and “What do I do next?” 80 | 81 | Some specific guidelines for writing sentences: 82 | 83 | ### Put action before outcome 84 | 85 | Start sentences with strong verbs that help users understand what they will do. Include what the outcome of the action will be. 86 | 87 | 88 | 89 | 90 | ### Yes 91 | - Add a book to your favourites. 92 | 93 | 94 | 95 | 96 | ### No 97 | - Books appear in your favourites after you add them. 98 | 99 | 100 | 101 | 102 | ### Use verbs instead of verb-noun phrases 103 | 104 | Look for combinations of verbs and nouns that can be replaced by a single verb. 105 | 106 | 107 | 108 | 109 | ### Yes 110 | - We will **refund** your subscription. 111 | 112 | 113 | 114 | 115 | ### No 116 | - We will **provide a refund** for your subscription. 117 | 118 | 119 | 120 | 121 | ### Don’t use permissive language 122 | 123 | Give users confidence about what they should do. 124 | 125 | 126 | 127 | 128 | ### Yes 129 | - Update your favourite list and get tailored recommendations for your next read. 130 | 131 | 132 | 133 | 134 | ### No 135 | - Update your favourite list and you can get the recommendations for books you’ll love most. 136 | 137 | 138 | 139 | 140 | ## Buttons 141 | Buttons are one of the most important elements in our digital platforms. Most actions and conversions are the direct result of the user clicking a button, and the labels used in buttons help users understand what actions are possible and are at the point when a decision becomes an action. 142 | 143 | Always write button labels in sentence case. 144 | 145 | 146 | 147 | 148 | ### Yes 149 | - Send suggestion 150 | 151 | 152 | 153 | 154 | ### No 155 | - Send Suggestion 156 | 157 | 158 | 159 | 160 | ### Buttons for action 161 | 162 | When buttons enable user actions, the formula is `verb` + `noun`. 163 | 164 | 165 | 166 | 167 | ### Yes 168 | - Add book 169 | - Explore recommendations 170 | 171 | 172 | 173 | 174 | ### No 175 | - Add 176 | - Explore 177 | 178 | 179 | 180 | 181 | ### Buttons in confirmations 182 | 183 | When buttons are used as calls to action in confirmations, the formula `verb` + `noun` doesn’t have to be followed. Instead, use a verb to clearly convey outcomes, by connecting action to outcome. 184 | 185 | 186 | 187 | 188 | ### Yes 189 | - Dicard changes? **Cancel / Discard** 190 | 191 | 192 | 193 | 194 | ### No 195 | - Discard changes? **No / Yes** 196 | 197 | 198 | 199 | 200 | ### Buttons for conversion 201 | 202 | When buttons support conversion goals, the formula is `value` + `relevance`. 203 | 204 | Instead of writing about what the user does (the action), write about what the user gets (the value). Then, make it more immediately relevant to the specific conversion context. 205 | 206 | The length of a button label when the button’s purpose is conversion isn’t usually a problem. 207 | 208 | 209 | 210 | 211 | ### Yes 212 | - Send a friend a free book 213 | 214 | 215 | 216 | 217 | ### No 218 | - Send gift 219 | 220 | 221 | 222 | 223 | **Further reading:** 224 | 225 | - [How to write a call to action that converts](https://unbounce.com/conversion-rate-optimization/how-to-write-a-call-to-action-that-converts-with-case-sudies) 226 | 227 | ### Buttons as microcopy 228 | 229 | Buttons can also be an opportunity to use microcopy to create a more positive experience (particularly in negative situations) by making the interface more human. If it’s appropriate, break the rules and explore ways to match the user’s feelings without assigning emotion. 230 | 231 | 232 | 233 | 234 | ### Yes 235 | - I tried, I’m stuck, I need help 236 | 237 | 238 | 239 | 240 | ### Instead of 241 | - Get help 242 | 243 | 244 | 245 | 246 | **Further reading:** 247 | - [Jason Fried on Twitter](https://twitter.com/jasonfried/status/1271886889159622659/photo/1) 248 | 249 | ## Links 250 | 251 | Links should provide information about what to expect about the associated actions or destination. Users should be able to predict what will happen as a result of clicking a link. 252 | 253 | 254 | 255 | 256 | ### Yes 257 | - View [publication information](#). 258 | 259 | 260 | 261 | 262 | ### No 263 | - Want publication information? [Click here](#). 264 | 265 | 266 | 267 | 268 | ### In sentences 269 | 270 | Links in full sentences shouldn’t link the whole sentence, but instead only the text that describes the outcome of clicking the link. 271 | 272 | 273 | 274 | 275 | ### Yes 276 | - Create your [favourites list](#). 277 | 278 | 279 | 280 | 281 | ### No 282 | - [Create your favourites list](#). 283 | 284 | 285 | 286 | 287 | ### Outside of sentences 288 | 289 | Links that aren’t in full sentences should be treated similarly to buttons, and use the `verb` + `noun` pattern. Don’t punctuate, except with questions marks. 290 | 291 | 292 | 293 | 294 | ### Yes 295 | - [Remove from list](#) 296 | - [Don’t have an account?](#) 297 | 298 | 299 | 300 | 301 | ### No 302 | - [Remove from list.](#). 303 | - [Don’t have an account](#) 304 | 305 | 306 | 307 | 308 | ## Error messages 309 | 310 | Error messages have three goals: 311 | 312 | - Explain simply and clearly that there is a problem and what that problem is. 313 | - Provide a solution so that users can return and complete the process immediately. 314 | - Turn the delay into an experience that is as pleasant as possible. 315 | 316 | Some specific things to keep in mind when writing error messages: 317 | 318 | ### Write without rigidity 319 | 320 | Keep it conversational. Adjusting the tone of voice to be more formal rather than casual is appropriate, but use restraint. 321 | 322 | 323 | 324 | 325 | ### Yes 326 | - Postal code should have 6 digits 327 | 328 | 329 | 330 | 331 | ### No 332 | - Postal code is invalid 333 | 334 | 335 | 336 | 337 | ### Write without using the words error or failure 338 | 339 | Again, keep it conversational. We’d never say “error!” in conversation if we didn’t understand something. 340 | 341 | 342 | 343 | 344 | ### Yes 345 | - Sorry, we couldn’t save your changes. Try again? 346 | 347 | 348 | 349 | 350 | ### No 351 | - Failed to save changes. 352 | 353 | 354 | 355 | 356 | ## Confirmation messages 357 | 358 | Confirmation messages and dialogues are shown when users take an action that can’t be undone, or is difficult to undo. 359 | 360 | Confirmation **messages** should: 361 | 362 | - Give the users a chance to confirm or cancel their action 363 | - Be shown in response to a single action 364 | - Always include one or two calls to action 365 | 366 | Confirmation message **titles** should: 367 | 368 | - Ask if they want to continue, using a `verb` + `noun` question. 369 | - Write in sentence case. Sentence case makes it feel less stiff and formal. 370 | - Don’t use punctuation like periods or semicolons, other than a question mark. 371 | 372 | 373 | 374 | 375 | ### Yes 376 | - Discard unsaved changes? 377 | 378 | 379 | 380 | 381 | ### No 382 | - Discard? 383 | 384 | 385 | 386 | 387 | Confirmation message **body content** should: 388 | 389 | - Use plain language to explain action is irreversible or difficult to undo. 390 | 391 | 392 | 393 | 394 | ### Yes 395 | - This can’t be undone 396 | 397 | 398 | 399 | 400 | ### No 401 | - If you discard changes, your list will remain unchanged. 402 | 403 | 404 | 405 | 406 | Confirmation message **calls to action** should: 407 | 408 | - Clearly convey outcomes, by connection action to outcome. 409 | 410 | 411 | 412 | 413 | ### Yes 414 | - Discard changes? **Cancel** / **Discard** 415 | 416 | 417 | 418 | 419 | ### No 420 | - Discard changes? **No** / **Yes** 421 | 422 | 423 | 424 | 425 | Special case: in the context of canceling accounts, use “Never mind” instead of “cancel” to prevent confusion. 426 | 427 | 428 | 429 | 430 | ### Yes 431 | - Never mind / Cancel account 432 | 433 | 434 | 435 | 436 | ### No 437 | - Cancel / Cancel account 438 | 439 | 440 | 441 | 442 | ## Success messages 443 | 444 | Success messages are shown to users in response to an action they performed, when it succeeds. 445 | 446 | Success messages have three goals: 447 | 448 | - Provide certainty to the users that the action was completed, and that everything is okay. 449 | - Provide next steps for users, whether optional or mandatory. 450 | - Provide positivity. When things go well, we can magnify the positive feeling and create affinity for our experience. 451 | 452 | Some specific things to keep in mind when writing success messages: 453 | 454 | ### Talk about or to the user, not about the action 455 | 456 | While it’s important to provide certainty that what the user wanted to do actually happened, it doesn’t need to be in the template “X successfully completed.” Sometimes, this certainty can even be implicit. 457 | 458 | Imagine if we let people share a book recommendation. When the recommendation is sent, we need to show a success message so they can be confident the recommendation was delivered. 459 | 460 | 461 | 462 | 463 | ### Yes 464 | - Recommendation sent! Sharing is caring. 465 | 466 | 467 | 468 | 469 | ### No 470 | - Recommendation successfully sent. 471 | 472 | 473 | 474 | 475 | ### If possible, provide a deeper and meaningful aspect of the action taken 476 | 477 | Use success messages to reinforce the value that our users get from taking a given action. 478 | 479 | If a user adds a book to their favourites list, treat the success message as an opportunity to reinforce that choice. 480 | 481 | 482 | 483 | 484 | ### Yes 485 | - Added to your favourites! Now others will be able to give you even better recommendations. 486 | 487 | 488 | 489 | 490 | ### No 491 | - Book added to your favourites list. 492 | 493 | 494 | 495 | 496 | ## Empty states 497 | 498 | Empty states are everywhere. They appear when there’s nothing to show. Sometimes, they appear when the user has yet to add something to a set of items. Other times, they appear when they try to find something and can’t. 499 | 500 | An empty state is a chance to educate users about what’s supposed to be there, what can be done there, and how it will provide value to the user. 501 | 502 | Some specific guidelines to keep in mind when writing content for empty states: 503 | 504 | ### Remember, empty states aren’t a bad thing 505 | 506 | Avoid using negative words or phrases. Empty states are transitional and reveal potential. Say what’s supposed to be here, or what can be done here, and how it will help them. 507 | 508 | 509 | 510 | 511 | ### Yes 512 | - Your favourite books will appear here! 513 | 514 | 515 | 516 | 517 | ### No 518 | - Oh no, there’re no books in your favourites list 519 | 520 | 521 | 522 | 523 | ### Provide a next step 524 | 525 | If relevant, tell users exactly how to start using a feature and provide an action they can take. 526 | 527 | 528 | 529 | 530 | ### Yes 531 | - New recommendations from other users will appear here. [Ask for your first recommendation!](#) 532 | 533 | 534 | 535 | 536 | ### No 537 | - No recommendations 538 | 539 | 540 | 541 | 542 | ## Placeholders 543 | 544 | Placeholders appear in form inputs before the user enters text. As a general rule of thumb, placeholders should be avoided except in very specific situations covered below. 545 | 546 | 547 | 548 | 549 | ### Use placeholders for: 550 | - Fields we really want users to complete. 551 | - Fields that users might not understand. 552 | 553 | 554 | 555 | 556 | ### Don’t use placeholders as: 557 | - Labels for form input fields. 558 | - Formatting guidelines. 559 | 560 | 561 | 562 | 563 | We use a few different approaches to placeholders. 564 | 565 | ### Questions 566 | 567 | When we really want users to complete a field, we can use placeholders to prompt a question for users to respond to. This should be direct and in the second person, and should have a simple, short answer. 568 | 569 | 570 | 571 | 572 | ### Yes 573 | - What’s your favourite book? 574 | 575 | 576 | 577 | 578 | ### Categories 579 | 580 | When the response to an input field might be from a wide range of options, placeholders can be used to reduce the number of choices and provide guidelines for how to respond to the field. 581 | 582 | 583 | 584 | 585 | ### Yes 586 | - Filter books by author, genre 587 | 588 | 589 | 590 | 591 | ### Examples 592 | 593 | Sometimes, an example will make it easier for users to understand how to answer a field. 594 | 595 | 596 | 597 | 598 | ### Yes 599 | - Such as “‘Harry Potter’ mixed with ‘A Gentleman in Moscow’” 600 | 601 | 602 | 603 | 604 | ## Helper messages 605 | 606 | Sometimes, our users need a bit of help. Helper messages help us support our users when they’re filling out forms or taking an action. 607 | 608 | Some specific guidelines for writing helper messages: 609 | 610 | ### Do users need help? 611 | 612 | Before adding helper messages, ask whether users actually need help. The more messages we include on a page or form, the more intimidating it appears to our users. 613 | 614 | ### Be concise 615 | 616 | This is particularly important for helper messages on input fields. 617 | 618 | 619 | 620 | 621 | ### Yes 622 | - 8–12 characters 623 | 624 | 625 | 626 | 627 | ### No 628 | - Password must be between 8–12 characters 629 | 630 | 631 | 632 | -------------------------------------------------------------------------------- /_docs/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | author: Quinn Keast 4 | date: 2020-02-17 5 | order: 01 6 | --- 7 | 8 |

A comprehensive set of useful and semi-universal UX copywriting and style guidelines and examples to reference while designing and building products and interfaces.

9 | 10 | Use these guidelines to: 11 | 12 | - **Create consistency** and deliver a usable, predictable, and on-brand experience. 13 | - **Empower your team** to make easier for anyone to write clear, effective, and useful content. 14 | - **Drive positive change** in language standards to create compassionate, inclusive, and respectful products. 15 | 16 | ## Fictional product: Foreword 17 | 18 | To provide realistic and useful examples throughout the guidelines, the language guidelines are built to support a fictional product called **Foreword**—a platform for people to ask for and share recommendations for books to read, based on shared reading history. All examples use this fictional product as a basis. 19 | 20 | ## Adapt and reuse for your own product 21 | 22 | These guidelines are available under an MIT license for adaptation and reuse for your own product. Find out more and get the framework on GitHub: [Product Language Framework](https://github.com/quinnkeast/product-language-framework). 23 | -------------------------------------------------------------------------------- /_docs/references.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: References, inspiration, and sources 3 | author: Quinn Keast 4 | date: 2020-02-17 5 | order: 07 6 | --- 7 | 8 | This guide is based on a variety of resources and experiences. Some specific resources that served as a source of inspiration: 9 | 10 | - [Mailchimp Content Style Guide](https://styleguide.mailchimp.com/) 11 | - [Shopify Polaris](https://polaris.shopify.com/) 12 | - [Wire writing guidelines](https://brand.wire.com/text/) 13 | - [Microcopy: The Complete Guide](https://www.microcopybook.com/) 14 | -------------------------------------------------------------------------------- /_docs/style-and-mechanics.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Style & mechanics 3 | author: Quinn Keast 4 | date: 2020-02-17 5 | order: 04 6 | --- 7 | 8 | import UsageBlock from "../components/UsageBlock.jsx"; 9 | import Usage from "../components/Usage.jsx"; 10 | 11 | We have an editorial style for our digital products to help us keep our copy clear and consistent, and specific guidelines to write and structure content to make it easy for users to understand and act on. 12 | 13 | ## Abbreviations & acronyms 14 | 15 | Avoid abbreviations and acronyms when possible. 16 | 17 | 18 | 19 | 20 | ### Yes 21 | - for example 22 | 23 | 24 | 25 | 26 | ### No 27 | - e.g. 28 | 29 | 30 | 31 | 32 | ## Adverbs and adjectives 33 | 34 | Try to avoid adjectives and adverbs. Using adjectives and adverbs (for example, “easy” read) shapes perception and sets expectations, and can inadvertently lead to a negative emotional experience for our users. 35 | 36 | If someone is a newer reader, then describing a book as “easy” might help them choose to read it. But if it takes them a long time to read it, they may feel bad because the book was supposed to be “easy,” and it still took them a while. 37 | 38 | Instead, look for opportunities to capture the same meaning through other forms. What makes an “easy” book easy relative to other books? Can that be used as a scale of relative difficulty? 39 | 40 | ## Active voice vs. passive voice 41 | 42 | Use active voice and avoid passive voice. In active voice, the subject of the sentence does the action. In passive voice, the subject of the sentence has the action done to it. 43 | 44 | 45 | 46 | 47 | ### Yes 48 | - Added book to list. 49 | 50 | 51 | 52 | 53 | ### No 54 | - The book was added to the list. 55 | 56 | 57 | 58 | 59 | Use passive voice when you don’t want to assign responsibility for an action. This can reduce tension in a message. 60 | 61 | 62 | 63 | 64 | ### Yes 65 | - Email address was incorrect. 66 | 67 | 68 | 69 | 70 | ### No 71 | - Invalid email address. 72 | 73 | 74 | 75 | 76 | ## Present tense 77 | 78 | Use present tense to describe the result of actions. 79 | 80 | 81 | 82 | 83 | ### Yes 84 | - Book added 85 | 86 | 87 | 88 | 89 | ### No 90 | - Book has been added 91 | 92 | 93 | 94 | 95 | ## Capitalization 96 | 97 | We use different forms of capitalization depending on the context. 98 | 99 | For example, use title case for book names, and use sentence case for book descriptions. (If you’re not sure which capitalization to use, check the design system for existing conventions.) 100 | 101 | All-caps should be used sparingly and only for specific purposes (all-caps copy is less accessible and harder to read). Never use all-caps within a sentence. 102 | 103 | ## Contractions 104 | 105 | Use contractions like “we’ll” or “there’s!” They make our writing more conversational. 106 | 107 | However, don’t rely on contractions alone for a conversational voice. When we translate copy for our global audience, they almost won’t carry over. 108 | 109 | ## Conversational writing 110 | 111 | Our voice is conversational. When we’re talking, we connect words with articles like “the,” “for,” “these,” and “an.” If we remove these from our writing, it makes our copy feel stiff and complicated. We can be flexible, though. If space is a limitation, the article can be omitted. 112 | 113 | 114 | 115 | 116 | ### Yes 117 | - Choose a book 118 | - Save your changes 119 | 120 | 121 | 122 | 123 | ### No 124 | - Choose book 125 | - Save changes 126 | 127 | 128 | 129 | 130 | ## Avoid directional language 131 | 132 | Avoid any instructions or language that requires the user to see the layout or design of a page or element. 133 | 134 | 135 | 136 | 137 | ### Yes 138 | - Choose a new book. 139 | 140 | 141 | 142 | 143 | ### No 144 | - Choose a new book at the bottom right of this page. 145 | 146 | 147 | 148 | 149 | ## Numbers 150 | 151 | Use the numeral when numbers appear in copy. This includes ordinals (numbers that tell the position of something in a list). 152 | 153 | 154 | 155 | 156 | ### Yes 157 | - You have 3 new recommendations 158 | - 2 days left to share a recommendation 159 | - This is your 3rd recommendation 160 | 161 | 162 | 163 | 164 | ### No 165 | - You have three new recommendations 166 | - Two days left to share a recommendation 167 | - This is your third recommendation 168 | 169 | 170 | 171 | 172 | When writing in English, numbers over 3 digits get commas. 173 | 174 | 175 | 176 | 177 | ### Yes 178 | - 144 179 | - 1,200 180 | 181 | 182 | 183 | 184 | When writing in languages like German, Swedish, and Dutch, numbers over 3 digits get a period. 185 | 186 | 187 | 188 | 189 | ### Yes 190 | - 144 191 | - 1.200 192 | 193 | 194 | 195 | 196 | ### Dates 197 | 198 | Spell out the day of the week and the month whenever possible. Abbreviate when space is a limitation. When abbreviated in interface headings and labels, do not use a period. 199 | 200 | The order of day and month should match the user’s locale. 201 | 202 | 203 | 204 | 205 | ### Yes 206 | - **USA:** 207 | Thursday, April 16 208 | April 16 209 | Apr 16 210 | - **Germany:** 211 | Thursday, 16 April 212 | 16 April 213 | 16 Apr 214 | 215 | 216 | 217 | 218 | When possible, use natural language to describe dates. 219 | 220 | 221 | 222 | 223 | ### Yes 224 | - Today 225 | - Yesterday 226 | - Tomorrow 227 | - This week 228 | - Next week 229 | - Two weeks from now 230 | 231 | 232 | 233 | 234 | ### Fractions 235 | 236 | Use typographic fractions when referencing measurements in ingredients. Don’t spell fractions out. 237 | 238 | 239 | 240 | 241 | ### Yes 242 | - ½ 243 | 244 | 245 | 246 | 247 | ### No 248 | - 1/2 249 | - one-half 250 | 251 | 252 | 253 | 254 | ### Percentages 255 | 256 | Use the % symbol instead of spelling out “percent.” 257 | 258 | 259 | 260 | 261 | ### Yes 262 | - 2% 263 | 264 | 265 | 266 | 267 | ### No 268 | - Two percent 269 | 270 | 271 | 272 | 273 | ### Ranges and spans 274 | 275 | Use an en dash (`–`) to indicate a range or span of numbers. Do not use spaces before and after the en dash. 276 | 277 | 278 | 279 | 280 | ### Yes 281 | - 10–20 minute read 282 | 283 | 284 | 285 | 286 | An en dash is slightly wider than a hyphen (`-`) but narrower than an em dash (`—`). 287 | 288 | ### Money 289 | 290 | When writing about US, CA, or AU currency, use the dollar sign ($) before the amount. Always include a decimal and number of cents. Do not insert a space between the dollar sign and the number. 291 | 292 | 293 | 294 | 295 | ### Yes 296 | - $10.00 297 | - $42.99 298 | 299 | 300 | 301 | 302 | When writing about EUR currency, use the Euro sign (€) after the amount. This applies in English, Dutch, Irish, and Maltese. 303 | 304 | In all other official EU languages the order is reversed: first the amount, then a space and the Euro sign. 305 | 306 | Always include a comma and number of cents. Note that English, Irish, and Maltese use a period instead of a comma. Look up local languages to confirm the correct separator for decimals. 307 | 308 | 309 | 310 | 311 | ### Yes 312 | - **In English, Dutch, Irish, and Maltese** 313 | €10,00 or €10.00 314 | €42,99 or €42.99 315 | 316 | - **In all other official EU languages** 317 | 10,00 € 318 | 42,99 € 319 | 320 | 321 | 322 | 323 | ### Telephone numbers 324 | 325 | Use dashes without spaces between numbers. Don’t use a country code. 326 | 327 | 328 | 329 | 330 | ### Yes 331 | - 123-456-7890 332 | - 012-345-678-910 333 | - 02-345-6789 334 | 335 | 336 | 337 | 338 | ### Temperature 339 | 340 | Use the degree symbol and capital C abbreviation for Celsius (everywhere except the USA) or capital F abbreviation for Fahrenheit (USA only). Don’t insert spaces between the number and the abbreviation. 341 | 342 | 343 | 344 | 345 | ### Yes 346 | - 220°C 347 | - 425°F 348 | 349 | 350 | 351 | 352 | ### Time 353 | 354 | When writing about a time of the day, use numerals and lowercase am or pm, with a space in between. Use 12- or 24-hour time according to the locale. When writing in 24-hour time, show a `0` before single-digit hours. 355 | 356 | 357 | 358 | 359 | ### Yes 360 | - 5 pm 361 | - 8:30 pm 362 | - 06:30 363 | 364 | 365 | 366 | 367 | ### No 368 | - 5pm 369 | - 8:30PM 370 | - 6:30h 371 | 372 | 373 | 374 | 375 | Use an en dash (`–`) between times to indicate a time period. Don’t insert spaces before and after the en dash. 376 | 377 | 378 | 379 | 380 | ### Yes 381 | - 8 am–12 pm 382 | - 06:30–18:00 383 | 384 | 385 | 386 | 387 | When referring to an amount of time, use numerals and the full word, with a space in between. 388 | 389 | 390 | 391 | 392 | ### Yes 393 | - Sent 20 minutes ago 394 | - Received 30 seconds ago 395 | 396 | 397 | 398 | 399 | ### Units of measurement 400 | 401 | In all cases, include a space between the number and the unit of measurement. 402 | 403 | 404 | 405 | 406 | ### Yes 407 | - 200 g 408 | 409 | 410 | 411 | 412 | ### No 413 | - 200g 414 | 415 | 416 | 417 | 418 | ## Punctuation 419 | 420 | ### Apostrophes 421 | 422 | Apostrophes are usually used to make a word possessive. If the word already ends in an s and it’s singular, you also add an’s. If the word ends in an s and is plural, just add an apostrophe. 423 | 424 | Watch out for dumb apostrophes (`'`). These are a relic of typewriters, and can be identified by how they’re straight rather than curly. Always use typographic apostrophes (`‘`). 425 | 426 | 427 | 428 | 429 | ### Yes 430 | - This recommendation’s new for you 431 | - This book is Jen’s favourite 432 | - Chris’s favourite book 433 | 434 | 435 | 436 | 437 | ### Colons 438 | 439 | Use a colon to offset a list. 440 | 441 | 442 | 443 | 444 | ### Yes 445 | - This books is known for three things: aliens, adventure, and bad poetry. 446 | 447 | When a list begins with an interface label, capitalize the first word of the list. 448 | 449 | - Genres: Fantasy, science fiction, and humour. 450 | 451 | 452 | 453 | 454 | ### Commas 455 | 456 | Use the Oxford comma when writing a list. 457 | 458 | 459 | 460 | 461 | ### Yes 462 | - Common tropes include the big bad, Mordor, and spy speak. 463 | 464 | 465 | 466 | 467 | ### No 468 | - Common tropes include the big bad, Mordor and spy speak. 469 | 470 | 471 | 472 | 473 | ### Dashes and hyphens 474 | 475 | Use a hyphen (`-`) without spaces before and after to link words into a single phrase. 476 | 477 | 478 | 479 | 480 | ### Yes 481 | - First-time reader? 482 | 483 | 484 | 485 | 486 | Use an en dash (`–`) to indicate a [range or span](#ranges-and-spans), without spaces. 487 | 488 | 489 | 490 | 491 | ### Yes 492 | - 2–6 minute read 493 | 494 | 495 | 496 | 497 | Use an em (`—`) dash without spaces to separate clauses in paragraph copy. 498 | 499 | 500 | 501 | 502 | ### Yes 503 | - This new series—a first in science fiction—is perfect for readers looking for bad poetry. 504 | 505 | 506 | 507 | 508 | ### Ellipses 509 | 510 | Use ellipses to indicate that a line of copy has been clipped before the end. This is typically used when only a single line of copy will fit, but the content itself is too long. Avoid this when possible. 511 | 512 | When ellipses are used to clip a line of copy, clip after at least 2 characters. 513 | 514 | 515 | 516 | 517 | ### Yes 518 | - Harry Potter and the Go… 519 | 520 | 521 | 522 | 523 | ### No 524 | - Harry Potter and the G… 525 | 526 | 527 | 528 | 529 | ### Periods 530 | 531 | Use periods in complete sentences. Don’t use periods in headlines or labels. Periods go inside quotation marks. They go outside parentheses ( ) when the contained text is part of a larger sentence, and inside when the contained text stands alone. 532 | 533 | 534 | 535 | 536 | ### Yes 537 | - 3 books on your list 538 | - We’ve updated the results to display only your selected languages (English, German). 539 | 540 | 541 | 542 | 543 | ### No 544 | - 3 meals in your box. 545 | 546 | 547 | 548 | 549 | ### Quotation marks 550 | 551 | Watch out for dumb quotation marks (`'` and `"`). These are a relic of typewriters, and can be identified by how they’re straight rather than curly. Always use typographic quotation marks (`‘` and `’`, `“` and `”`). 552 | 553 | 554 | 555 | 556 | ### Yes 557 | - ‘Example’ 558 | - “Example Two” 559 | 560 | 561 | 562 | 563 | ### No 564 | - 'Example' 565 | - "Example two" 566 | 567 | 568 | 569 | 570 | ## Places 571 | 572 | Spell out all city names. 573 | 574 | When written in paragraph copy, write out country, state, and province names on the first mention. On subsequent mentions, abbreviating is fine. 575 | 576 | 577 | 578 | 579 | ### Yes 580 | - Now available in Winnipeg. 581 | 582 | 583 | 584 | 585 | ### No 586 | - Now available in WPG. 587 | 588 | 589 | 590 | 591 | ## Positive vs. negative 592 | 593 | Use positive language rather than negative language. Positives are easier to read and process than negatives. One way to detect negative language is to look for words like “can’t,” “don’t,” etc. 594 | 595 | 596 | 597 | 598 | ### Yes 599 | - Update your payment method to access your account. 600 | 601 | 602 | 603 | 604 | ### No 605 | - If you don’t update your payment method, you won’t be able to access your account. 606 | 607 | 608 | 609 | 610 | ## Use positive words when talking about positive things 611 | 612 | If a sentence begins with a negative word, the sentence’s implication can be misinterpreted as negative. 613 | 614 | 615 | 616 | 617 | ### Yes 618 | - Remember to save your new payment method 619 | 620 | 621 | 622 | 623 | ### No 624 | - Don’t forget to save your new payment method 625 | 626 | 627 | 628 | 629 | ## Writing questions 630 | 631 | A common error when writing questions is not constructing the sentence as a question. Usually, this can be fixed by changing the word order. 632 | 633 | 634 | 635 | 636 | ### Yes 637 | - Why does “Dune” deserve a re-read? 638 | 639 | 640 | 641 | 642 | ### No 643 | - Why “Dune” deserves a re-read? 644 | 645 | 646 | 647 | 648 | ## Writing about ourselves 649 | 650 | We have a few different names depending on whether we’re referring to our legal entity or our trade name, and which market we’re writing for. 651 | 652 | In our digital products, use our trade name for each market: 653 | 654 | 655 | 656 | 657 | ### Yes 658 | - **CA & USA:** Foreword 659 | - **EU:** Forworded 660 | 661 | 662 | 663 | 664 | Always title case our name. Don’t abbreviate our name. 665 | 666 | 667 | 668 | 669 | ### Yes 670 | - Foreword 671 | 672 | 673 | 674 | 675 | ### No 676 | - FW 677 | 678 | 679 | 680 | 681 | Don’t capitalize descriptive product or feature names. 682 | 683 | 684 | 685 | 686 | ### Yes 687 | - our mobile app 688 | - reading recommendations 689 | - your list 690 | 691 | 692 | 693 | 694 | ### No 695 | - our Mobile App 696 | - Reading Recommendations 697 | - your List 698 | 699 | 700 | 701 | 702 | ## Writing about people 703 | 704 | We design and build our platforms with a human-centered point of view. Whenever we write something for our digital platforms, we need to write in a way that’s compassionate, inclusive, and respectful. 705 | 706 | Here are some specific guidelines for how we write about people. 707 | 708 | ### Disability 709 | 710 | Avoid disability-related idioms like “lame” or “falling on deaf ears.” 711 | 712 | Default to person-first language rather than identity-first language. 713 | 714 | 715 | 716 | 717 | ### Yes 718 | - they have a disability 719 | - person with disability 720 | - people living with disabilities 721 | 722 | 723 | 724 | 725 | ### No 726 | - they are disabled 727 | - disabled person 728 | 729 | 730 | 731 | 732 | Do not use the term “edge cases” to describe users living with disabilities. This term further marginalizes people already living on the margins. Instead, use the term “stress cases.” 733 | 734 | ### Gender and sexuality 735 | 736 | Don’t call groups of people “guys.” Don’t call women “girls.” 737 | 738 | Avoid gendered terms. 739 | 740 | 741 | 742 | 743 | ### Yes 744 | - Artisan 745 | 746 | 747 | 748 | 749 | ### No 750 | - Craftsman 751 | 752 | 753 | 754 | 755 | It’s okay to use “they” as a singular pronoun. If there’s no inherent purpose to specifying a gender, default to “they.” 756 | 757 | ### Names 758 | 759 | Prefer gender neutral names when referencing fictional individuals. Use names from diverse cultural backgrounds. 760 | 761 | 762 | 763 | 764 | ### Yes 765 | - Alex, Cameron, Charan, Jordan, Kai, Logan, Morgan, Quinn, Remy, Sacha 766 | 767 | 768 | 769 | 770 | ### No 771 | - Alice, Charles, Elizabeth, James, John, Margaret, Mary, Robert, Sarah, William 772 | 773 | 774 | 775 | 776 | ### Hearing 777 | 778 | Use lower-case “deaf,” “hard of hearing,” or “hearing loss” as adjectives to describe someone with hearing loss. 779 | 780 | ### Vision 781 | 782 | Use lower-case “blind” to describe someone who is unable to see. Use “low vision” or “vision loss” as adjectives to describe a person with limited vision. 783 | 784 | ## Avoid words with multiple meanings 785 | 786 | Some words to watch out for: 787 | 788 | **Once** (could mean “one time,” “after,” “in the past,” or “when”) 789 | 790 | 791 | 792 | 793 | ### Yes 794 | - After you’ve made your picks 795 | 796 | 797 | 798 | 799 | ### No 800 | - Once you’ve made your picks 801 | 802 | 803 | 804 | 805 | **Right** (could mean “correct,” “the opposite of left,” “politically conservative,” etc.) 806 | 807 | 808 | 809 | 810 | ### Yes 811 | - Pick the book you like most 812 | 813 | 814 | 815 | 816 | ### No 817 | - Pick the right book 818 | 819 | 820 | 821 | 822 | **Since** (could refer to a point in time, or a synonym of “because”) 823 | 824 | 825 | 826 | 827 | ### Yes 828 | - Because you have a referral already, you can invite a friend and get a free month 829 | 830 | 831 | 832 | 833 | ### No 834 | - Since you already have a referral, you can invite a friend and get a free month 835 | 836 | 837 | 838 | 839 | **“Require” plus an infinitive** (could confuse the relationship between subject and object) 840 | 841 | 842 | 843 | 844 | ### Yes 845 | - Referrals can be sent from any Foreword account. 846 | 847 | 848 | 849 | 850 | ### No 851 | - A Foreword account is required to send referrals. _(This could imply that both the sender and receiver have to have a Foreword account to send a referral.)_ 852 | 853 | 854 | 855 | 856 | ## Avoid idioms 857 | 858 | In most languages, idioms are commonly-known phrases packed with meaning. However, idioms don’t often translate into other languages. They can also create confusion for translators that have English as an additional language. It’s better to avoid using idioms at all. 859 | 860 | 861 | 862 | 863 | ### Yes 864 | - Let’s get started 865 | - This information might change 866 | 867 | 868 | 869 | 870 | ### No 871 | - Let’s get crackin’ 872 | - Take this with a grain of salt 873 | 874 | 875 | 876 | -------------------------------------------------------------------------------- /_docs/voice-and-tone.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Voice & tone 3 | author: Quinn Keast 4 | date: 2020-02-17 5 | order: 02 6 | --- 7 | 8 | import UsageBlock from "../components/UsageBlock.jsx"; 9 | import Usage from "../components/Usage.jsx"; 10 | 11 | One of the ways we create a consistent and familiar experience for our users is by speaking with a consistent voice throughout our product and content while being aware of our tone. 12 | 13 | ## What’s the difference between voice and tone? 14 | 15 | What’s the difference between voice and tone? Think of it this way: our **voice** is a personification of who we are. We always have the same voice. But, our **tone** changes to match our context and situation, usually through word choice. 16 | 17 | For example, when our users are asked to provide their first book suggestion for someone looking for something to read, they might be feeling excited and maybe a bit daunted. We can cheer them on and thank them when they send a suggestion. 18 | 19 | But when our users experience something going poorly, such as well-thought suggestion failing to send, they’re probably feeling annoyed and uncertain. We can recognize what went wrong, apologize, and tell them what will happen next. 20 | 21 | ## Voice guidelines 22 | 23 | Our brand has a distinct voice and character. This voice and character is the basis of our writing for all of our digital platforms. 24 | 25 | When we write in our shared voice, we are always: 26 | 27 | ### Sage 28 | 29 | - Create a positive and relaxed tone for our readers 30 | - Assume positive intent, even when things go wrong 31 | 32 | ### Curious 33 | 34 | - Express our real interest and curiosity 35 | - Be an active listener for our users 36 | 37 | ### Confident 38 | 39 | - Communicate clearly and honestly 40 | - Be plain-spoken 41 | - Let our love for reading and stories shine through 42 | 43 | ## Using tone 44 | 45 | Our tone is more than just the words we choose: it’s the way that we choose to share our personality through our voice. 46 | 47 | We can express any given message in wildly different ways. To find the right tone for any given message, consider the four dimensions of tone: 48 | 49 | 1. Funny vs. serious. 50 | 2. Formal vs. casual. 51 | 3. Respectful vs. irreverent. 52 | 4. Enthusiastic vs. matter-of-fact. 53 | 54 | Vary these dimensions to meet the user’s emotional and situational context at a given moment in time. Note that our brand voice has a “range” along these dimensions, and we should aim to stay within that range to keep our voice and character consistent. 55 | 56 | **Further Reading** 57 | - [The Four Dimensions of Tone of Voice](https://www.nngroup.com/articles/tone-of-voice-dimensions/) by NNG 58 | 59 | ## Adapting to emotional context 60 | 61 | ### Positive situations 62 | 63 | 64 | 65 | 66 | ### Do 67 | - Be encouraging and positive 68 | - Express interest 69 | 70 | 71 | 72 | 73 | ### Don’t 74 | - Don’t take credit for their success 75 | - Don’t assume it was easy 76 | 77 | 78 | 79 | 80 | ### Neutral situations 81 | 82 | 83 | 84 | 85 | ### Do 86 | - Express confidence 87 | - Connect value to action 88 | 89 | 90 | 91 | 92 | ### Don’t 93 | - Don’t create uncertainty 94 | - Don’t tell people “what to do” 95 | 96 | 97 | 98 | 99 | ### Negative situations 100 | 101 | 102 | 103 | 104 | ### Do 105 | - Be honest and direct about problems and provide next steps 106 | - Use conversational but straightforward language 107 | 108 | 109 | 110 | 111 | ### Don’t 112 | - Don’t minimize the problem 113 | - Don’t use negative or technical words 114 | - Don’t assign emotion 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /_docs/word-list.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Word list 3 | author: Quinn Keast 4 | date: 2020-02-17 5 | order: 06 6 | --- 7 | 8 | We use certain words in a special way. This glossary defines our terminology to make sure we all use the same words for the same things. 9 | 10 | The list below includes deprecated terms that are either ambiguous or obsolete and should be replaced with the corresponding → **preferred term**. 11 | 12 | ## Things 13 | 14 | - account 15 | - plan 16 | - favourites list 17 | - favourite 18 | - recommendation 19 | - request 20 | - collection 21 | - book 22 | - title 23 | - author 24 | - genre 25 | - publisher 26 | - description 27 | - cover 28 | - publish date 29 | - page count 30 | - run → **series** 31 | - rating 32 | - review 33 | - payment method 34 | - refund 35 | 36 | ## Verbs 37 | 38 | - OK vs. accept 39 | - Close vs. cancel 40 | - Done vs. back vs never mind 41 | - Choose vs. select 42 | - Edit vs. manage 43 | - Choose vs. swap 44 | - Swap vs. switch 45 | - View vs. see 46 | - Need vs. must 47 | - Export vs. download 48 | - Share vs. refer 49 | 50 | ## Tricky words 51 | 52 | - email 53 | - homepage 54 | - login (noun, adjective), log in (verb) 55 | - OK 56 | - opt-in (noun, adjective), opt in (verb) 57 | - never mind (verb) 58 | - signup (noun, adjective), sign up (verb) 59 | - username 60 | - URL 61 | - website 62 | 63 | ## Avoid these words 64 | 65 | - **Good read** (avoid words owned by competitors) 66 | -------------------------------------------------------------------------------- /components/Content.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo, useEffect } from 'react'; 2 | import Sidebar from './Sidebar'; 3 | import { getMDXComponent } from "mdx-bundler/client"; 4 | 5 | const Content = (props) => { 6 | const { page, pages, path } = props; 7 | 8 | function getAnchor(text) { 9 | return text 10 | .toLowerCase() 11 | .replace(/[^a-z0-9 ]/g, '') 12 | .replace(/[ ]/g, '-'); 13 | } 14 | 15 | const H2 = ({ children }) => { 16 | const anchor = getAnchor(children); 17 | const link = `#${anchor}`; 18 | return ( 19 |

20 | 21 | {children} 22 |

23 | ); 24 | }; 25 | 26 | const ContentComponent = useMemo(() => getMDXComponent(page.content), [page.content]); 27 | 28 | return ( 29 |
30 | 38 |
39 |

{page.title}

40 | 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default Content; 51 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | const Footer = () => ( 2 |
3 |

Created by Quinn Keast. View on GitHub.

4 |
5 | ); 6 | 7 | export default Footer; -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | const Header = () => ( 4 |
5 | 6 | Pixel art of pencil writing helper text on an input 7 | UX Language 8 | 9 |
10 | ); 11 | 12 | export default Header; 13 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | import Footer from './Footer'; 3 | import Meta from './meta'; 4 | 5 | const Layout = (props) => { 6 | return ( 7 |
8 | 9 |
10 | {props.children} 11 |
13 | ); 14 | } 15 | 16 | export default Layout; -------------------------------------------------------------------------------- /components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import { useRouter } from 'next/router'; 4 | 5 | const ActiveLink = ({ children, ...props }) => { 6 | const router = useRouter(); 7 | let className = props.className || ''; 8 | if (router.asPath === props.as && props.activeClassName) { 9 | className = `${className} ${props.activeClassName}`.trim(); 10 | } 11 | 12 | delete props.activeClassName; 13 | 14 | return {children}; 15 | } 16 | 17 | const genericHamburgerLine = `h-0.5 w-5 my-0.5 rounded-full bg-gray-300 transition ease transform duration-300`; 18 | 19 | const Sidebar = (props) => { 20 | const pages = props.pages.sort((a, b) => Number(a.order) - Number(b.order)); 21 | 22 | const [toggled, setToggled] = React.useState(false); 23 | 24 | const Nav = ({pages, props}) => { 25 | return ( 26 | <> 27 | {pages.map(page => ( 28 |
29 | 37 | <>{page.title} 38 | 39 | {(page.slug === props.currentPage) && props.sections &&
40 | {props.sections.map(section => {section.text} 41 | )} 42 |
} 43 |
44 | ))} 45 | 46 | ) 47 | }; 48 | 49 | return ( 50 |
51 | 70 |
71 | ); 72 | } 73 | 74 | export default Sidebar; -------------------------------------------------------------------------------- /components/Usage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { getMDXComponent } from "mdx-bundler/client"; 3 | 4 | function parseHeading(props) { 5 | const children = props.children.map(child => 6 | {child.props.value} 7 | ); 8 | return children; 9 | } 10 | 11 | const Usage = (props) => { 12 | const content = props.children; 13 | 14 | //const ContentComponent = useMemo(() => getMDXComponent(content), [content]); 15 | 16 | return ( 17 |
18 | {content} 19 |
20 | ); 21 | }; 22 | 23 | export default Usage; 24 | -------------------------------------------------------------------------------- /components/UsageBlock.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const UsageBlock = ({ children }) => { 4 | return
{children}
; 5 | }; 6 | 7 | export default UsageBlock; 8 | -------------------------------------------------------------------------------- /components/meta.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | 3 | import { HOME_OG_IMAGE_URL, SITE_NAME } from "../lib/constants"; 4 | 5 | export default function Meta() { 6 | return ( 7 | 8 | {SITE_NAME} 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 33 | 34 | 35 | ); 36 | } -------------------------------------------------------------------------------- /data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Product Language Framework", 3 | "description": "Mucking about" 4 | } -------------------------------------------------------------------------------- /lib/api.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { join } from "path"; 3 | import matter from "gray-matter"; 4 | 5 | const contentDirectory = join(process.cwd(), "_docs"); 6 | 7 | export function getPageSlugs() { 8 | return fs.readdirSync(contentDirectory); 9 | } 10 | 11 | export function getPageBySlug(slug, fields = []) { 12 | const realSlug = slug.replace(/\.mdx$/, ""); 13 | const fullPath = join(contentDirectory, `${realSlug}.mdx`); 14 | const fileContents = fs.readFileSync(fullPath, "utf8"); 15 | const { data, content } = matter(fileContents); 16 | 17 | const items = {}; 18 | 19 | fields.forEach((field) => { 20 | if (field === "slug") { 21 | items[field] = realSlug; 22 | } 23 | if (field === "content") { 24 | items[field] = content; 25 | } 26 | if (data[field]) { 27 | items[field] = data[field]; 28 | } 29 | }); 30 | 31 | return items; 32 | } 33 | 34 | export function getAllPages(fields = []) { 35 | const slugs = getPageSlugs(); 36 | const pages = slugs 37 | .map((slug) => getPageBySlug(slug, fields)) 38 | return pages; 39 | } -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | export const SITE_NAME = "UX Language"; 2 | export const SITE_URL = "https://uxlanguage.com"; 3 | export const HOME_OG_IMAGE_URL = "https://uxlanguage.com/assets/social-preview.png"; -------------------------------------------------------------------------------- /lib/mdxToHtml.js: -------------------------------------------------------------------------------- 1 | import { bundleMDX } from "mdx-bundler"; 2 | 3 | export default async function mdxToHtml(content) { 4 | const { code } = await bundleMDX({ 5 | source: content, 6 | }); 7 | return code; 8 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: (config, { isServer}) => { 3 | // Fixes npm packages that depend on `fs` module 4 | if (!isServer) { 5 | config.resolve.fallback.fs = false; 6 | } 7 | 8 | return config; 9 | } 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-language-framework", 3 | "version": "1.1.0", 4 | "description": "", 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "keywords": [], 11 | "author": "Quinn Keast", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@mdx-js/loader": "^2.3.0", 15 | "@next/mdx": "^13.4.6", 16 | "esbuild": "^0.18.6", 17 | "gray-matter": "^4.0.2", 18 | "htmr": "^1.0.2", 19 | "mdx-bundler": "^9.2.1", 20 | "next": "^13.4.6", 21 | "raw-loader": "^4.0.1", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-markdown": "^8.0.7", 25 | "react-sticky": "^6.0.3", 26 | "react-toggle": "^4.1.1" 27 | }, 28 | "devDependencies": { 29 | "@babel/plugin-transform-react-jsx": "^7.22.5", 30 | "@babel/preset-react": "^7.22.5", 31 | "postcss-preset-env": "^8.5.1", 32 | "tailwindcss": "^3.3.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/[slug].js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/Layout'; 2 | import Content from '../components/Content'; 3 | import Head from 'next/head'; 4 | import mdxToHtml from "../lib/mdxToHtml"; 5 | import { getPageBySlug, getAllPages } from "../lib/api"; 6 | import { SITE_NAME } from "../lib/constants"; 7 | 8 | export default function Page({ page, pages }) { 9 | return( 10 | 11 | 12 | {`${SITE_NAME} | ${page.title}`} 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export async function getStaticProps({ params }) { 20 | const pages = await getAllPages([ 21 | "slug", 22 | "title", 23 | "order", 24 | ]); 25 | 26 | const page = await getPageBySlug(params.slug, [ 27 | "title", 28 | "order", 29 | "content", 30 | ]); 31 | 32 | const content = await mdxToHtml(page.content); 33 | 34 | const getSections = (source) => { 35 | const regex = /^##\s+(.*)$/gm; 36 | 37 | if (source.match(regex)) { 38 | return source.match(regex).map((heading) => { 39 | const headingText = heading.replace(/^##\s+/, ''); 40 | const link = '#' + headingText.replace(/ /g, '-').toLowerCase(); 41 | 42 | return { 43 | text: headingText, 44 | link, 45 | }; 46 | }); 47 | } 48 | return []; 49 | }; 50 | 51 | const sections = getSections(page.content); 52 | const slug = params.slug; 53 | 54 | return { 55 | props: { 56 | page: { 57 | ...page, 58 | content, 59 | sections, 60 | slug, 61 | }, 62 | pages, 63 | }, 64 | } 65 | } 66 | 67 | export async function getStaticPaths() { 68 | const pages = await getAllPages(["slug"]); 69 | 70 | return { 71 | paths: pages.map((page) => { 72 | return { 73 | params: { 74 | slug: page.slug, 75 | } 76 | } 77 | }), 78 | fallback: false, 79 | }; 80 | } -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/style.css'; 2 | 3 | export default function LanguageFrameworkApp({ Component, pageProps }) { 4 | return( 5 | 6 | ); 7 | } -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | 4 | // Redirect '/' to /overview so that the markdown docs just work 5 | export default function Index() { 6 | const router = useRouter(); 7 | 8 | useEffect(() => { 9 | const {pathname} = router; 10 | if (pathname === '/') { 11 | router.push('/overview'); 12 | } 13 | }, []); 14 | 15 | return (null); 16 | } 17 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /public/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinnkeast/product-language-framework/d3507681652eef8362ce8255205b48e6b95fbc31/public/assets/favicon.ico -------------------------------------------------------------------------------- /public/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /public/assets/social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinnkeast/product-language-framework/d3507681652eef8362ce8255205b48e6b95fbc31/public/assets/social-preview.png -------------------------------------------------------------------------------- /styles/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | h1 { 6 | @apply text-2xl md:text-4xl font-bold mb-6 text-gray-900; 7 | } 8 | 9 | h2 { 10 | @apply text-xl md:text-2xl font-bold mt-12 text-gray-900; 11 | } 12 | 13 | h2 .anchor { 14 | position: relative; 15 | } 16 | 17 | h2 .anchor:before { 18 | @apply text-2xl font-normal text-gray-500; 19 | content: "#"; 20 | position: absolute; 21 | left: -1.5rem; 22 | } 23 | 24 | h2 .anchor:hover:before { 25 | @apply text-blue-600; 26 | } 27 | 28 | h3 { 29 | @apply text-lg font-bold mt-8 text-gray-900; 30 | } 31 | 32 | p { 33 | @apply text-base mt-4 text-gray-800; 34 | } 35 | 36 | ol { 37 | @apply mt-4 list-decimal list-inside; 38 | } 39 | 40 | ol li { 41 | @apply text-gray-800 pt-2; 42 | } 43 | 44 | ol li:first-of-type { 45 | @apply pt-0; 46 | } 47 | 48 | ul { 49 | @apply mt-4 list-disc list-outside; 50 | } 51 | 52 | ul li { 53 | @apply text-gray-800 pt-2 pl-1 ml-4; 54 | } 55 | 56 | ul li::marker{ 57 | 58 | } 59 | 60 | ul li:first-of-type { 61 | @apply pt-0; 62 | } 63 | 64 | a { 65 | @apply text-blue-600 hover:text-blue-400 focus:outline; 66 | } 67 | 68 | .usage { 69 | @apply pt-4 pr-6 pb-6 pl-6 rounded-b-lg bg-gray-50; 70 | } 71 | 72 | .usage h3 { 73 | @apply text-xs mt-0 text-gray-600 tracking-wider uppercase; 74 | } 75 | 76 | .usage li { 77 | @apply text-gray-800; 78 | } 79 | 80 | .usage-yes { 81 | @apply border-t-2 border-green-400; 82 | } 83 | 84 | .usage-no { 85 | @apply border-t-2 border-red-400; 86 | } 87 | 88 | code { 89 | @apply p-1 rounded bg-purple-100 text-purple-600 text-base; 90 | } 91 | 92 | :focus { 93 | @apply outline outline-2 outline-offset-2 rounded-sm; 94 | } 95 | 96 | blockquote { 97 | @apply bg-purple-100 rounded-lg pt-2 pl-6 pr-6 pb-6; 98 | } 99 | 100 | blockquote p, blockquote li { 101 | @apply text-purple-900; 102 | } 103 | 104 | #sidebar::-webkit-scrollbar { 105 | width: 0.4rem; 106 | } 107 | 108 | #sidebar::-webkit-scrollbar-track { 109 | @apply bg-gray-100; 110 | } 111 | 112 | #sidebar::-webkit-scrollbar-thumb { 113 | @apply bg-gray-300 rounded-md; 114 | } 115 | 116 | #sidebar::-webkit-scrollbar-thumb:hover { 117 | @apply bg-gray-400; 118 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 4 | "./components/**/*.{js,ts,jsx,tsx,mdx}" 5 | ], 6 | theme: { 7 | screens: { 8 | 'sm': '640px', 9 | // => @media (min-width: 640px) { ... } 10 | 11 | 'md': '768px', 12 | // => @media (min-width: 768px) { ... } 13 | 14 | 'lg': '1024px', 15 | // => @media (min-width: 1024px) { ... } 16 | } 17 | }, 18 | plugins: [], 19 | } 20 | --------------------------------------------------------------------------------