Piyush Gambhir

Front-end JavaScript/TypeScript Code Formatting

17 min readPiyush Gambhir
JavaScriptTypeScriptCode FormattingFront-end

Frontend projects (e.g. React or Next.js apps) often involve JavaScript/TypeScript, JSX/TSX, and CSS (sometimes via Tailwind). The de-facto standard for formatting these is Prettier – an opinionated code formatter widely used in the JS ecosystem. Prettier enforces a consistent style by parsing code and re-printing it with its own rules, ensuring everyone’s code looks the same regardless of who wrote it. Below, we’ll cover a typical Prettier configuration (including specific settings and plugins for React/Next and Tailwind CSS), explain each option’s purpose, and provide best practices for using Prettier in frontend development.

Prettier Configuration Basics

Let’s start with a common Prettier config for Node/TypeScript frontends and break down what each setting does. Assume we have a prettier.config.js or .prettierrc with content based on a provided template:

js
1/** @type {import('prettier').Config} */
2module.exports = {
3  endOfLine: 'lf',
4  semi: false,
5  singleQuote: false,
6  tabWidth: 2,
7  trailingComma: 'es5',
8  importOrder: [
9    '^(react/(.*)$)|^(react$)',
10    '^(next/(.*)$)|^(next$)',
11    '<THIRD_PARTY_MODULES>',
12    '',
13    '^types$',
14    '^@/types/(.*)$',
15    '^@/config/(.*)$',
16    '^@/lib/(.*)$',
17    '^@/hooks/(.*)$',
18    '^@/components/ui/(.*)$',
19    '^@/components/(.*)$',
20    '^@/registry/(.*)$',
21    '^@/app/(.*)$',
22    '',
23    '^(?!.*[.]css$)[./].*$',
24    '.css$',
25  ],
26  importOrderSeparation: false,
27  importOrderSortSpecifiers: true,
28  importOrderBuiltinModulesToTop: true,
29  importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
30  importOrderMergeDuplicateImports: true,
31  importOrderCombineTypeAndValueImports: true,
32  plugins: [
33    require.resolve('@ianvs/prettier-plugin-sort-imports'),
34    require('prettier-plugin-tailwindcss'),
35  ],
36};

(The above is an example configuration that we will dissect below.)

Now, what do these settings mean?

  • tabWidth: 2 – Sets the number of spaces per indentation level to 2. In practice, this means each indent in your code (for example, inside a function or block) uses two spaces. This is a common choice in JavaScript/TypeScript projects for readability. Consistently using 2 spaces (instead of a mix of 2, 4, or tabs) prevents misaligned code across editors.
  • semi: false – If false, Prettier will omit semicolons at the ends of statements where they are optional. Many JS style guides (like StandardJS) prefer no semicolons, while others include semicolons for clarity. Prettier allows either style; here the project has chosen no semicolons for a cleaner look. Prettier will still insert semicolons where required to avoid ASI (Automatic Semicolon Insertion) pitfalls, but otherwise you won’t see semicolons at end of lines.
  • singleQuote: false – This setting dictates string quotation style. false means use double quotes for strings in JavaScript. If it were true, Prettier would use single quotes. This is largely a preference – some projects prefer single quotes for JS (to avoid needing to escape quotes inside HTML attributes, for example), while others use double quotes. The key is consistency. Here, double quotes will be standardized for all string literals.
  • trailingComma: 'es5' – This option controls trailing commas in multi-line lists/objects. 'es5' means add trailing commas where valid in ES5 (objects, arrays, etc.) but not in function parameter lists. For example, an array might format as:
    js
    1const arr = [
    2  'one',
    3  'two',
    4  'three', // <- trailing comma added
    5];
    Trailing commas are useful because they produce cleaner diffs (adding a new item only changes one line) and minimize syntax errors from missing commas. The es5 setting is a balanced choice – it adds trailing commas in places that all modern JS runtimes accept (like object literals, array literals, imports/exports). (Prettier also has "all" to include function arguments, which needs ES2017 support. Here we stick to ES5-safe commas.)
  • endOfLine: 'lf' – Enforces Unix-style line endings (LF – line feed). This is important for cross-OS teams (Windows vs Unix) to avoid git diffs from mismatched CRLF vs LF. Setting it to 'lf' normalizes all newlines to \n. This prevents issues where a file might be edited on Windows (which might default to CRLF) and then cause unnecessary diffs on Linux/macOS. Consistent line endings also help tooling and CI. In short, use LF for consistency unless there’s a specific reason to use CRLF.

Why these core settings? They establish a baseline style: 2-space indent, no semicolons, double quotes, trailing commas in objects/arrays, and consistent newlines. This matches many modern JS styleguides and helps catch errors (e.g., trailing commas avoid forgetting a comma when reordering lines). These preferences improve diff clarity (trailing commas, no semis reduce line churn) and keep code style tidy.

Sorting Imports Automatically (Import Order Plugin)

Maintaining a sorted and organized list of imports at the top of files improves readability – related imports are grouped and the overall structure is clearer. The above config uses @ianvs/prettier-plugin-sort-imports (a popular Prettier plugin) to automate import ordering. The settings related to import sorting are:

  • importOrder – an array of regex patterns that define the desired order of import groups. The plugin will sort imports by matching each import statement against these patterns in sequence. In our example, the order is: React imports first, then Next.js imports, then third-party modules, then a blank line, then various internal modules (like types, config, lib, hooks, components, etc. under a project-specific @/ alias), then another blank line, then relative imports (./ or ../), and finally CSS imports. The special token '<THIRD_PARTY_MODULES>' is used as a placeholder for “all other external dependencies” not covered by earlier patterns. Blank string entries ("") in the array indicate where to insert an empty line separation. In summary, this example grouping achieves:
    1. Framework imports (React, Next) at the top (often you want core libraries first).
    2. Third-party packages (npm dependencies) next.
    3. Separated by a blank line, then internal project imports (grouped by categories like types, config, components, etc., as defined by path aliases @/...).
    4. Another blank line, then relative imports (imports from nearby files using ./ or ../ paths) – separated into non-CSS vs CSS so that style imports come last.
    5. CSS imports last (often you import styles after logic). This structured approach keeps imports tidy: external libraries are clearly distinguished from internal code, and side-effect imports (like importing a CSS file or polyfill) can be pushed to the bottom where they’re expected. Any imports that don’t match a specific regex fall under the <THIRD_PARTY_MODULES> bucket by default and appear in the third-party section (at the top).
  • importOrderSeparation: false – When true, this option automatically inserts a blank line between import groups. In our case, we have explicit "" entries in the order list to handle separation, so we set this to false (to avoid double blank lines). If you didn’t include "" in the array and wanted each distinct regex group separated, you could set this to true. Since we precisely control where blank lines go in importOrder, we disable automatic separation.
  • importOrderSortSpecifiers: true – This will sort the named imports within curly braces. For example, if you have import { B, A, C } from 'module';, it will reformat to import { A, B, C } from 'module';. Sorting specifiers makes it easier to see what’s imported and avoid duplicates. We enable it for tidy alphabetical ordering of import names.
  • importOrderBuiltinModulesToTop: true – When true, Node’s builtin modules (like fs, path, etc.) are grouped separately and placed before other third-party imports. This is useful in Node projects; in a browser-only project this might not matter. In our configuration, since we include <THIRD_PARTY_MODULES>, this flag ensures Node core modules are treated as a distinct group at the very top of third-party imports. Essentially, if you import any Node built-in, it won’t be mingled with random package imports; it will bubble up to the top of the list.
  • importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'] – These tell the import sorting plugin which parser features to use for understanding the code. Since we have a TypeScript React project, we include typescript and jsx so it can parse those syntax features. decorators-legacy is included if we use legacy decorator syntax. This ensures the plugin can parse import statements in .ts/.tsx files without issues.
  • importOrderMergeDuplicateImports: true – If set, the plugin will combine duplicate import sources into one. For example, if two imports from the same module exist, it will merge them into a single import statement (merging specifiers). This cleans up accidental duplicate imports.
  • importOrderCombineTypeAndValueImports: true – TypeScript allows importing types separately using the import type { Foo } from '...' syntax. This option will combine type imports and value imports from the same module into one statement when possible. It prevents having two lines importing from the same module (one for types, one for values); instead you'll get one line importing both, which is neater.

All these options work in concert via the sort-imports plugin. The goal is to keep the import section organized and avoid manual sorting. Notably, the plugin is careful not to reorder imports that have side effects. For example, if you have an import that runs some setup code (without exporting anything), it won't move it below imports that depend on that side effect. One of the plugin’s features is “no re-ordering across side-effect imports” – it preserves the relative order of import statements if moving them could change runtime behavior. It also handles comments around imports gracefully so that important comments (like // polyfill) stick with their import. This means you can safely sort imports without breaking your code.

Tip: Define your import order regex to match your project structure. In our example, @/hooks/(.*)$ corresponds to an alias for internal hooks. You’d adjust these patterns based on your own folder aliases or keep it simple (e.g., group all internal imports as a single category). The provided example is quite granular for a Next.js app; smaller projects might just distinguish third-party vs local imports.

Tailwind CSS Class Sorting (Tailwind Prettier Plugin)

If your frontend uses Tailwind CSS, you’ll accumulate long className strings with many utility classes. Ensuring these classes are consistently ordered (e.g., grouped by similar function or by responsive modifiers) improves readability and can catch duplicates. Rather than manually ordering classes, the official prettier-plugin-tailwindcss automates this.

By including require('prettier-plugin-tailwindcss') in the Prettier plugins array, we enable automatic Tailwind class sorting. The Tailwind plugin scans your HTML/JSX for class attributes and reorders the classes according to Tailwind’s recommended sort order. Tailwind’s recommended order groups classes by their function (layout, display, spacing, colors, etc.) and by responsiveness (e.g., all sm: variants together). The plugin ensures that no matter how developers type the classes, on save they’ll be sorted in a logical, consistent sequence.

What this means in practice: If a developer writes <div className="bg-blue-500 text-center px-4 my-2 text-sm md:my-4">…</div>, Prettier will rearrange the classes (without altering the output) to, for example: <div className="bg-blue-500 px-4 my-2 md:my-4 text-center text-sm">…</div>. This makes the class list easier to scan (all spacing classes grouped, responsive variants grouped, etc.). It also prevents situations where the same class might be repeated or conflicting classes are far apart. The end result is consistent and readable HTML/CSS in your JSX. According to Tailwind Labs, this plugin “automatically sorts classes based on our recommended class order”, so you don’t have to think about it.

Using this plugin has several benefits:

  • Consistency: All developers get the same class order, which is easier to compare in diffs. No more bikeshedding about how to order Tailwind utilities.
  • Maintenance: It’s easier to spot if a class is duplicated or contradictory (like p-4 and p-8 on the same element) when similar classes are grouped together.
  • Integration: It works with your existing Prettier setup, so class sorting happens on save/format along with all other formatting.

Tip: The Tailwind plugin may require specifying your Tailwind config or CSS path if you use Tailwind v4 or have a custom config (using the tailwindConfig or tailwindStylesheet options). This ensures the plugin knows about your custom classes or theme modifications. In most Next.js apps, it will pick up the tailwind.config.js automatically if it’s in the project root, but check the plugin docs if sorting isn’t happening.

Prettier Best Practices for Frontend Projects

Using Prettier in a frontend project is straightforward, but a few tips and best practices can help you get the most out of it:

  • Minimal Custom Configuration: Prettier is intentionally opinionated – it has a limited set of options by design. Embrace those defaults where possible. The configuration we discussed tweaks a few preferences (like quotes and semicolons), but avoid over-customizing. The whole point is consistency and reducing decision fatigue. As one developer noted, Prettier “makes a lot of hard-coded decisions to provide a minimal configuration interface... code [is] consistent across projects”, at the expense of fine-grained tweaks. This is usually a net positive: it saves time arguing about style.
  • Editor Integration (Format on Save): Set up your IDE or editor to run Prettier on file save. In VS Code, for example, install the Prettier extension and enable “Format on Save”. This way, every time you save a file, it’s automatically formatted. This immediate feedback loop conditions developers to write formatted code and eliminates any excuse for not running the formatter. Other editors (WebStorm, Sublime, etc.) have similar integration or can use Prettier via CLI on save. The Prettier documentation provides guidelines for editor integration.
  • Combine with ESLint effectively: It’s common to use ESLint for linting (catching code issues, enforcing best practices) and Prettier for formatting. To avoid conflicts (where both try to enforce style), use an ESLint config like eslint-config-prettier which turns off stylistic lint rules that Prettier covers. This ensures ESLint focuses on code correctness and unused vars, while Prettier handles spaces, commas, quotes, etc. The synergy is: ESLint can auto-fix some things, but defers purely stylistic ones to Prettier. This split keeps your setup simpler. In a Next.js/React app, you might run eslint --fix and prettier --write as separate steps (or use eslint-plugin-prettier to run Prettier via ESLint, though that’s less common now).
  • Run Prettier in a Pre-commit Hook: To guarantee code is formatted before it’s committed to git, integrate Prettier with a pre-commit hook tool like Husky (for Git hooks) + lint-staged. Husky can trigger a script on commit, and lint-staged will only pass changed files to Prettier (for speed). Prettier’s docs even include a one-line setup: npx mrm@2 lint-staged – which will install Husky and lint-staged and configure a pre-commit hook to run Prettier on staged files . With this in place, if a developer forgets to format, the hook will format the code (or fail the commit if not formatted, depending on config) automatically. This _“automatically format[s] supported files in a pre-commit hook”, ensuring consistency.
  • CI Enforcement: It’s wise to also have a check in your Continuous Integration pipeline. For example, run prettier --check . (which checks if files are correctly formatted) as part of your CI tests. If someone bypassed the pre-commit or their editor wasn’t set up, CI will catch it. This acts as a safety net so that the main branch always adheres to the formatting standard. Some teams even have a CI job that runs Prettier and fails if changes are produced, indicating that the contributor needs to run Prettier. The bottom line is to make it impossible to merge unformatted code.
  • Tailor import ordering to your project: The import sort patterns given in the example are quite specific. Adjust them to match your project’s module structure. If you don’t have complicated aliases, you might just use something like:
    json
    1"importOrder": [ "^react", "^next", "<THIRD_PARTY_MODULES>", "", "^[./]" ],
    2"importOrderSeparation": true,
    3"importOrderSortSpecifiers": true
    This simpler setup would put React/Next first, then other packages, blank line, then any relative or project imports (since ^[./] matches anything starting with . or / which would catch relative paths or absolute paths without distinguishing). The key is the principle: group imports logically (standard library vs external vs internal) and alphabetize within groups. It improves clarity at the top of your files.
  • Understand Prettier’s Limitations: Prettier deliberately doesn’t give you control over every formatting detail. Occasionally, you might disagree with its choices – for instance, how it breaks a long JSX prop onto multiple lines, or its handling of arrow function parentheses. Remember that Prettier’s philosophy is that automated consistency is better than hand-tuned perfection. It might not format everything exactly how you’d do it by hand, but the benefit of having zero manual formatting work (and zero debates) is worth the trade-off. If something is truly problematic (like a comment or code gets misformatted), there are escape hatches: you can use // prettier-ignore before a line or /* prettier-ignore-start/end */ around a block to opt out of formatting for that section. Use these rarely – only when absolutely necessary (e.g., special formatting in a markdown code block or tricky template literals).
  • Pros & Cons Awareness: Using Prettier has clear advantages for frontend code, but also a few downsides. We’ll summarize these below.

Pros and Cons of Using Prettier (Frontend)

Prettier Pros:

  • Unified Code Style: Prettier enforces one consistent style. It “removes all original styling” and outputs code that conforms to a single style guide. This means all developers’ code looks the same, which is great for teamwork and readability.
  • No More Style Debates: By being opinionated, Prettier ends arguments about spacing, commas, quotes, etc. Developers can’t fight the formatter, so they usually just accept its conventions. This eliminates a common source of friction in code reviews. As noted earlier, this leads to fewer trivial comments and more focus on important issues.
  • Saves Time: You no longer need to spend time manually aligning brackets or adding spaces – Prettier does it on save. Over a large project, this automation saves significant developer hours (and mental energy). It also likely reduces the time spent in code review addressing style.
  • Improved Readability: Despite some criticisms (see cons), many Prettier decisions are intended to improve readability. For example, it often breaks long chains or objects into multi-line for clarity, and it adds trailing commas to make diffs cleaner. Prettier’s output is generally nicely indented and spaced for human reading. One blog noted “many of its choices are well-intentioned and make code much easier to read”.
  • Multi-language support: Prettier doesn’t just format JS/TS – it also formats JSON, YAML, Markdown, HTML, CSS, GraphQL, etc. Using Prettier, you can have one formatter for your entire frontend stack (styles, config files, documentation markdown). This consistency extends beyond just your TypeScript code.
  • Ecosystem and Integration: Prettier is very popular, which means it’s well-maintained and integrates with nearly every editor and CI system. It has plugins for specialized needs (like the Tailwind plugin we used, or plugins for other frameworks). The wide adoption means new team members likely already know Prettier, and many boilerplate projects (Create React App, Next.js templates, etc.) come with Prettier ready to go.
  • Stability and Semantics: Prettier intentionally does not change the semantics of your code – it only affects whitespace, punctuation, and formatting. It won’t rename variables or rewrite logic. For instance, unlike some tools, it won’t reorder your JavaScript statements. It’s purely about style, which means you can trust that running Prettier won’t break your code (assuming no Prettier bugs). This is analogous to Python’s Black promise: format without changing the code’s behavior.

Prettier Cons:

  • Opinionated (Lack of Flexibility): The flip side of being opinionated is that you might not like every rule Prettier enforces, and you have limited recourse. For example, you can’t tell Prettier to use 3 spaces indent, or to break lines a different way than it chooses (beyond the general printWidth setting). If Prettier’s style clashes with a team’s desired style, you basically have to either live with it or not use Prettier. As one developer put it, “you are losing the ability to have fine-grained tweaks to how your code should look”. For most, the consistency is worth the sacrifice in flexibility, but it’s a con if you have strong stylistic preferences.
  • Line Breaking Noise: Prettier’s automatic line wrapping by printWidth can sometimes make diffs harder to read. For instance, if you add one character to a long line and it crosses the 80-character threshold, Prettier might reformat the entire line into a multi-line block. The actual change (one character) is obscured by a big diff of the line breaking differently. This can complicate code reviews (diff viewers show large changes even for a small edit). Some devs find that these “noisy diffs” hurt readability of version history. Essentially, a tiny code change might cascade into reflows of code that wasn’t logically changed. Prettier’s stance is that consistent wrapping is still better in the long run, but it’s a valid critique that diffs can be noisier.
  • One Style Doesn’t Fit All?: In a few cases, Prettier’s defaults might not be universally loved. E.g., it always formats ternary operators in a certain expanded style or forces parentheses in arrow functions only when needed. Some developers might find certain Prettier-formatting less intuitive. However, Prettier’s widespread use suggests most teams are okay with its style, even if it’s not their personal favorite, because the benefits outweigh the discomfort. It’s a con to consider if your team has very unique style guidelines that Prettier can’t satisfy.
  • Integration with Other Tools: If not configured properly, Prettier can conflict with linters (e.g., ESLint rules might disagree with Prettier formatting). This is solvable (as discussed: use eslint-config-prettier, etc.), but it’s a “con” in the sense that you need to set it up. If someone isn’t aware, they might double-format or get confused by ESLint vs Prettier differences. There’s also a small learning curve for new devs to know to install the editor plugin or run Prettier. Fortunately, documentation and community support is plentiful here.
  • Performance on Large Files: Prettier is not the fastest tool (it works by parsing to an AST and has complex printing logic). On extremely large files, it might be a bit slow (taking a couple of seconds). Usually this isn’t an issue – typical files format in milliseconds – but in rare cases, you might hit performance issues. There have been improvements over time, and the benefits still trump this minor con for most. Just be aware if you have a 5k-line file, Prettier might take longer to process it.

Overall, Prettier’s pros make it a strong choice for frontend code formatting. The key is to integrate it in a way that it becomes a background tool – always running, seldom thought about – so developers just write code and trust that it will be styled correctly. This yields a cleaner codebase, happier developers (once they’re used to it), and smoother code reviews.