Quick Setup Files
-
Tailwind Prose
styles/typography.css
@utility prose { --prose-color: var(--color-stone-700); --prose-heading-color: var(--color-stone-950); --prose-strong-color: var(--color-stone-950); --prose-link-color: var(--color-stone-950); --prose-code-color: var(--color-stone-950); --prose-link-underline-color: var(--color-violet-400); --prose-th-borders: var(--color-stone-300); --prose-td-borders: var(--color-stone-200); --prose-hr-color: color-mix(in oklab, var(--color-stone-950) 10%, transparent); --prose-blockquote-border-color: var(--color-stone-300); --prose-marker-color: color-mix( in oklab, var(--color-stone-700) 25%, transparent ); --prose-marker-decimal-color: color-mix( in oklab, var(--color-stone-700) 80%, transparent ); &:where(.dark, .dark *) { --prose-color: var(--color-stone-300); --prose-heading-color: var(--color-white); --prose-strong-color: var(--color-white); --prose-link-color: var(--color-white); --prose-code-color: var(--color-white); --prose-marker-color: color-mix( in oklab, var(--color-stone-300) 35%, transparent ); --prose-link-underline-color: var(--color-sky-400); --prose-th-borders: var(--color-stone-600); --prose-td-borders: var(--color-stone-700); --prose-hr-color: color-mix(in oklab, var(--color-white) 10%, transparent); --prose-blockquote-border-color: var(--color-stone-600); } @media (prefers-color-scheme: dark) { &:where(.system, .system *) { --prose-color: var(--color-stone-300); --prose-heading-color: var(--color-white); --prose-strong-color: var(--color-white); --prose-link-color: var(--color-white); --prose-code-color: var(--color-white); --prose-marker-color: color-mix( in oklab, var(--color-stone-300) 35%, transparent ); --prose-link-underline-color: var(--color-sky-400); --prose-th-borders: var(--color-stone-600); --prose-td-borders: var(--color-stone-700); --prose-hr-color: color-mix(in oklab, var(--color-white) 10%, transparent); --prose-blockquote-border-color: var(--color-stone-600); } } color: var(--prose-color); font-size: var(--text-lg); *:where(:not(.not-prose, .not-prose *)) + *:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 6); } h1:where(:not(.not-prose, .not-prose *)) { font-size: var(--text-2xl); font-weight: var(--font-weight-semibold); text-wrap: balance; } h2:where(:not(.not-prose, .not-prose *)) { font-size: var(--text-lg); line-height: calc(28 / 18); letter-spacing: -0.025em; color: var(--prose-code-color); font-weight: var(--font-weight-semibold); margin-top: calc(var(--spacing) * 20); } h2:has(+ h3):where(:not(.not-prose, .not-prose *)) { line-height: 2; font-weight: var(--font-weight-medium); font-family: var(--font-mono); font-variant-ligatures: none; letter-spacing: 0.1em; color: var(--prose-color); text-transform: uppercase; } h3:where(:not(.not-prose, .not-prose *)) { color: var(--prose-heading-color); font-weight: var(--font-weight-semibold); margin-top: calc(var(--spacing) * 16); } h2 + h3:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 6); } h4:where(:not(.not-prose, .not-prose *)) { color: var(--prose-heading-color); font-weight: var(--font-weight-medium); margin-top: calc(var(--spacing) * 12); } :is(h2, h3, h4):where(:not(.not-prose, .not-prose *)) { scroll-margin-top: calc(var(--spacing) * 32); @variant lg { /* biome-ignore lint/suspicious/noDuplicateProperties: Valid overwrite in tw */ scroll-margin-top: calc(var(--spacing) * 18); } } ol:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 4); padding-left: calc(var(--spacing) * 6); list-style-type: decimal; } ol li:where(:not(.not-prose, .not-prose *)) { padding-left: calc(var(--spacing) * 3); } ol li + li:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 4); } ol li:where(:not(.not-prose, .not-prose *))::marker { font-weight: var(--font-weight-semibold); color: var(--prose-marker-decimal-color); } ul:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 4); padding-left: calc(var(--spacing) * 6); list-style-type: disc; } ul li:where(:not(.not-prose, .not-prose *)) { padding-left: calc(var(--spacing) * 3); } ul li:where(:not(.not-prose, .not-prose *))::marker { color: var(--prose-marker-color); font-size: calc(var(--spacing) * 6); } ul li + li:where(:not(.not-prose, .not-prose *)) { margin-top: var(--spacing); } a:not(:where(:is(h2, h3, h4) *)):where(:not(.not-prose, .not-prose *)) { color: var(--prose-link-color); font-weight: var(--font-weight-semibold); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--prose-link-underline-color); text-decoration-thickness: 1px; & code { font-weight: var(--font-weight-semibold); } } a:hover:where(:not(.not-prose, .not-prose *)) { text-decoration-thickness: 2px; } a:where(:not(.not-prose, .not-prose *)):has(> [data-media]) { display: block; } strong:where(:not(.not-prose, .not-prose *)) { color: var(--prose-strong-color); font-weight: var(--font-weight-semibold); } code:where(:not(.not-prose, .not-prose *)) { font-variant-ligatures: none; font-family: var(--font-mono); font-weight: var(--font-weight-medium); color: var(--prose-code-color); } :where(h2, h3, h4) code:where(:not(.not-prose, .not-prose *)) { font-weight: var(--font-weight-semibold); } code:where(:not(.not-prose, .not-prose *))::before, code:where(:not(.not-prose, .not-prose *))::after { display: inline; content: "`"; } pre:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 4); margin-bottom: calc(var(--spacing) * 10); } pre code * + *:where(:not(.not-prose, .not-prose *)) { margin-top: 0; } pre code:where(:not(.not-prose, .not-prose *))::before, pre code:where(:not(.not-prose, .not-prose *))::after { content: none; } pre code:where(:not(.not-prose, .not-prose *)) { font-variant-ligatures: none; font-family: var(--font-mono); font-size: var(--text-sm); line-height: 2; } table:where(:not(.not-prose, .not-prose *)) { width: 100%; table-layout: auto; margin-top: 2em; margin-bottom: 2em; font-size: var(--text-sm); line-height: 1.4; } thead:where(:not(.not-prose, .not-prose *)) { border-bottom-width: 1px; border-bottom-color: var(--prose-th-borders); } thead th:where(:not(.not-prose, .not-prose *)) { color: var(--prose-heading-color); font-weight: 600; vertical-align: bottom; padding-inline-end: 0.6em; padding-bottom: 0.8em; padding-inline-start: 0.6em; } thead th:first-child:where(:not(.not-prose, .not-prose *)) { padding-inline-start: 0; } thead th:last-child:where(:not(.not-prose, .not-prose *)) { padding-inline-end: 0; } tbody tr:where(:not(.not-prose, .not-prose *)) { border-bottom-width: 1px; border-bottom-color: var(--prose-td-borders); } tbody tr:last-child:where(:not(.not-prose, .not-prose *)) { border-bottom-width: 0; } tbody td:where(:not(.not-prose, .not-prose *)) { vertical-align: baseline; } tfoot:where(:not(.not-prose, .not-prose *)) { border-top-width: 1px; border-top-color: var(--prose-th-borders); } tfoot td:where(:not(.not-prose, .not-prose *)) { vertical-align: top; } tbody td:where(:not(.not-prose, .not-prose *)), tfoot td:where(:not(.not-prose, .not-prose *)) { padding-top: 0.8em; padding-inline-end: 0.6em; padding-bottom: 0.8em; padding-inline-start: 0.6em; } tbody td:first-child:where(:not(.not-prose, .not-prose *)), tfoot td:first-child:where(:not(.not-prose, .not-prose *)) { padding-inline-start: 0; } tbody td:last-child:where(:not(.not-prose, .not-prose *)), tfoot td:last-child:where(:not(.not-prose, .not-prose *)) { padding-inline-end: 0; } th:where(:not(.not-prose, .not-prose *)), td:where(:not(.not-prose, .not-prose *)) { text-align: start; } td code:where(:not(.not-prose, .not-prose *)) { font-size: 0.8125rem; } hr:where(:not(.not-prose, .not-prose *)) { border-color: var(--prose-hr-color); margin-block: --spacing(14); & + h2 { margin-top: --spacing(14); } } blockquote { font-style: italic; border-inline-start-width: 0.25rem; border-inline-start-color: var(--prose-blockquote-border-color); padding-inline-start: calc(var(--spacing) * 4); } figure:where(:not(.not-prose, .not-prose *)) { figcaption:where(:not(.not-prose, .not-prose *)) { margin-top: calc(var(--spacing) * 3); text-align: center; font-size: var(--text-sm); line-height: var(--text-sm--line-height); font-style: italic; color: color-mix(in oklab, var(--prose-color) 75%, transparent); } } :first-child:where(:not(.not-prose, .not-prose *)) { margin-top: 0; } :last-child:where(:not(.not-prose, .not-prose *)) { margin-bottom: 0; } } @utility prose-blog { img:where(:not(.not-prose, .not-prose *)) { /* biome-ignore lint/correctness/noUnknownFunction: CSS function */ @media (max-width: theme(--breakpoint-sm)) { margin-inline: calc(var(--spacing) * -4); max-width: calc(100% + calc(var(--spacing) * 8)); } } } -
VSCode Config
.vscode/settings.json
{ "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": "always", "source.fixAll.biome": "explicit", "source.organizeImports.biome": "explicit" }, "workbench.editor.customLabels.patterns": { "**/app/**/page.tsx": "${dirname} (page)", "**/app/**/layout.tsx": "${dirname} (layout)" }, "javascript.preferences.importModuleSpecifierEnding": "auto", "typescript.preferences.importModuleSpecifierEnding": "auto", "typescript.tsdk": "node_modules/typescript/lib", "tailwindCSS.classFunctions": ["createVariants", "clsx", "cn"] } -
Biome Config
biome.json
{ "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, "files": { "ignoreUnknown": false, "includes": ["**", "!**/node_modules"] }, "formatter": { "enabled": true, "useEditorconfig": true, "formatWithErrors": false, "indentStyle": "space", "indentWidth": 2, "lineEnding": "lf", "lineWidth": 80, "attributePosition": "auto", "bracketSpacing": true }, "assist": { "actions": { "source": { "organizeImports": "on", "useSortedAttributes": "on" } } }, "linter": { "enabled": true, "rules": { "recommended": true, "correctness": { "noUnusedImports": { "level": "warn", "fix": "safe", "options": {} } }, "suspicious": { "noAlert": "warn" }, "nursery": { "noMagicNumbers": "warn", "noMisusedPromises": "warn", "noUnassignedVariables": "warn", "useUnifiedTypeSignature": "warn", "useSortedClasses": { "level": "info", "fix": "safe", "options": { "functions": ["cn"] } } }, "style": { "useFilenamingConvention": { "level": "warn", "options": { "requireAscii": true, "filenameCases": ["kebab-case"] } }, "noProcessEnv": "error" } } }, "javascript": { "formatter": { "jsxQuoteStyle": "double", "quoteProperties": "asNeeded", "trailingCommas": "es5", "semicolons": "always", "arrowParentheses": "always", "bracketSameLine": false, "quoteStyle": "double", "attributePosition": "auto", "bracketSpacing": true } }, "css": { "formatter": { "enabled": true } } } -
Prettier Config
.prettierrc.json
{ "semi": true, "singleQuote": false, "tabWidth": 2, "trailingComma": "es5", "tailwindFunctions": ["cn"], "importOrder": [ "server-only", "use client", "^(react|next?/?([a-zA-z/]*))$", "<THIRD_PARTY_MODULES>", "^@/lib/(.*)$", "^@/components/(.*)$", "^@/(.*)$", "^[./]" ], "importOrderSeparation": true, "importOrderSortSpecifiers": true, "importOrderCaseInsensitive": true, "importOrderGroupNamespaceSpecifiers": true, "plugins": [ "@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss" ] } -
ESLint Config
eslint.config.mjs
import { FlatCompat } from "@eslint/eslintrc"; import pluginJs from "@eslint/js"; import prettier from "eslint-config-prettier"; import checkFile from "eslint-plugin-check-file"; import n from "eslint-plugin-n"; import globals from "globals"; import path from "node:path"; import { fileURLToPath } from "node:url"; import pluginTs from "typescript-eslint"; const compat = new FlatCompat({ baseDirectory: path.dirname(fileURLToPath(import.meta.url)), }); /** @type {import('eslint').Linter.Config[]} */ export default [ { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, { plugins: { prettier, "check-file": checkFile, n, }, }, { rules: { "@typescript-eslint/no-unused-expressions": ["off"], "@typescript-eslint/ban-ts-comment": [ "error", { "ts-ignore": "allow-with-description", }, ], "import/no-anonymous-default-export": [ "error", { allowArray: true, }, ], "prefer-arrow-callback": ["error"], "prefer-template": ["error"], quotes: ["error", "double"], "no-unused-vars": [ "warn", { argsIgnorePattern: "^_[^_].*$|^_$", varsIgnorePattern: "^_[^_].*$|^_$", caughtErrorsIgnorePattern: "^_[^_].*$|^_$", }, ], "no-restricted-imports": [ "error", { patterns: [ { group: ["./", "../"], message: "Relative imports are not allowed.", }, ], }, ], "check-file/filename-naming-convention": [ "error", { "**/*.{ts,tsx}": "KEBAB_CASE", }, { ignoreMiddleExtensions: true, }, ], "check-file/folder-naming-convention": [ "error", { "src/**/!^[.*": "KEBAB_CASE", }, ], "n/no-process-env": ["error"], }, }, pluginJs.configs.recommended, ...pluginTs.configs.recommended, ...compat.extends("next/core-web-vitals"), ]; -
Terminal config (zsh)
~/.zshrc
# Find and set branch name var if in git repository. function git_branch_name() { branch=$(git symbolic-ref HEAD 2> /dev/null | awk 'BEGIN{FS="/"} {print $ if [[ $branch == "" ]]; then : else echo ' %F{#a5b4fc}['$branch']%f' fi } # Enable autocomplete autoload -Uz compinit && compinit # Enable substitution in the prompt setopt prompt_subst # Config for prompt. PS1 synonym. current_folder='%F{#fdba74}%1/%f' prompt='adrian@${current_folder}$(git_branch_name)$ ' #aliases alias pn="pnpm"