DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Cover image for My Neovim setup for React, TypeScript, Tailwind CSS, etc
Takuya Matsuyama
Takuya Matsuyama

Posted on • Originally published at blog.inkdrop.app

My Neovim setup for React, TypeScript, Tailwind CSS, etc

Hi, it's Takuya here.
As you may know, I mainly use Neovim to code my app called Inkdrop, a cross-platform Markdown note-taking app.
It's built with Electron for desktop and React Native for mobile platforms.
It's been 1 year since I last posted my Neovim setup.
Neovim and its plugins have been evolved so well.
So, I'd like to share my latest setup for coding React and TypeScript based apps.
The main difference is that the config files are now written in Lua.
I switched from vim-plug to Packer.
I also made a tutorial video on how to set up Neovim from scratch on a new M2 MacBook Air.
If you have your own dotfiles already, you can cherry-pick my config.
I hope you enjoy it!

Ingredients

Here is a quick summary of my set up:

And here is my dotfiles repository:

GitHub logo craftzdog / dotfiles-public

My personal dotfiles

fish screenshot

nvim screenshot

Takuya's dotfiles

Warning: Donโ€™t blindly use my settings unless you know what that entails. Use at your own risk!

Looking for a Markdown note-taking app?

Check out my app called Inkdrop

Inkdrop

Contents

  • vim (NeoVim) config
  • tmux config
  • git config
  • fish config
  • PowerShell config

Neovim setup

Requires Neovim (>= 0.7)

A tutorial video:

Prerequisites โ€” iTerm2 and Patched Nerd Font

iTerm2 is a fast terminal emulator for macOS.
Install one of Nerd Fonts for displaying fancy glyphs on your terminal.
My current choice is Hack.
And use it on your terminal app. For example, on iTerm2:

iTerm Preferences

Install Neovim via Homebrew

brew install neovim
Enter fullscreen mode Exit fullscreen mode

Directory structure

Neovim conforms XDG Base Directory structure. Here is my config file structure:

๐Ÿ“‚ ~/.config/nvim
โ”œโ”€โ”€ ๐Ÿ“ after
โ”‚  โ””โ”€โ”€ ๐Ÿ“ plugin
โ”œโ”€โ”€ ๐Ÿ“‚ lua
โ”‚  โ””โ”€โ”€ ๐ŸŒ‘ base.lua
โ”œโ”€โ”€ ๐Ÿ“ plugin
โ””โ”€โ”€ ๐Ÿ‡ป init.lua
Enter fullscreen mode Exit fullscreen mode

Neovim loads $HOME/.config/nvim/init.vim or init.lua first instead of $HOME/.vimrc.
Check out the quickstart guide for more detail:

https://github.com/nanotee/nvim-lua-guide

Install plugin manager: Packer

Install Packer by running the below command:

git clone --depth 1 https://github.com/wbthomason/packer.nvim \
 ~/.local/share/nvim/site/pack/packer/start/packer.nvim
Enter fullscreen mode Exit fullscreen mode

Then, make ./.config/nvim/lua/plugins.lua like so:

local status, packer = pcall(require, "packer")
if (not status) then
  print("Packer is not installed")
  return
end

vim.cmd [[packadd packer.nvim]]

packer.startup(function(use)
  use 'wbthomason/packer.nvim'
  -- Your plugins go here
end)
Enter fullscreen mode Exit fullscreen mode

Then, require it from init.lua like so:

require('plugins')
Enter fullscreen mode Exit fullscreen mode

Color scheme: Neosolarized

Neosolarized

I use svrana/neosolarized.nvim with some customizations.

local status, n = pcall(require, "neosolarized")
if (not status) then return end

n.setup({
  comment_italics = true,
})

local cb = require('colorbuddy.init')
local Color = cb.Color
local colors = cb.colors
local Group = cb.Group
local groups = cb.groups
local styles = cb.styles

Color.new('black', '#000000')
Group.new('CursorLine', colors.none, colors.base03, styles.NONE, colors.base1)
Group.new('CursorLineNr', colors.yellow, colors.black, styles.NONE, colors.base1)
Group.new('Visual', colors.none, colors.base03, styles.reverse)

local cError = groups.Error.fg
local cInfo = groups.Information.fg
local cWarn = groups.Warning.fg
local cHint = groups.Hint.fg

Group.new("DiagnosticVirtualTextError", cError, cError:dark():dark():dark():dark(), styles.NONE)
Group.new("DiagnosticVirtualTextInfo", cInfo, cInfo:dark():dark():dark(), styles.NONE)
Group.new("DiagnosticVirtualTextWarn", cWarn, cWarn:dark():dark():dark(), styles.NONE)
Group.new("DiagnosticVirtualTextHint", cHint, cHint:dark():dark():dark(), styles.NONE)
Group.new("DiagnosticUnderlineError", colors.none, colors.none, styles.undercurl, cError)
Group.new("DiagnosticUnderlineWarn", colors.none, colors.none, styles.undercurl, cWarn)
Group.new("DiagnosticUnderlineInfo", colors.none, colors.none, styles.undercurl, cInfo)
Group.new("DiagnosticUnderlineHint", colors.none, colors.none, styles.undercurl, cHint)
Enter fullscreen mode Exit fullscreen mode

Status line: Lualine

lualine

nvim-lualine/lualine.nvim provides a flexible way to configure statusline.

local status, lualine = pcall(require, "lualine")
if (not status) then return end

lualine.setup {
  options = {
    icons_enabled = true,
    theme = 'solarized_dark',
    section_separators = { left = '๎‚ฐ', right = '๎‚ฒ' },
    component_separators = { left = '๎‚ฑ', right = '๎‚ณ' },
    disabled_filetypes = {}
  },
  sections = {
    lualine_a = { 'mode' },
    lualine_b = { 'branch' },
    lualine_c = { {
      'filename',
      file_status = true, -- displays file status (readonly status, modified status)
      path = 0 -- 0 = just filename, 1 = relative path, 2 = absolute path
    } },
    lualine_x = {
      { 'diagnostics', sources = { "nvim_diagnostic" }, symbols = { error = '๏†ˆ ', warn = '๏ฑ ', info = '๏‘‰ ',
        hint = '๏ƒซ ' } },
      'encoding',
      'filetype'
    },
    lualine_y = { 'progress' },
    lualine_z = { 'location' }
  },
  inactive_sections = {
    lualine_a = {},
    lualine_b = {},
    lualine_c = { {
      'filename',
      file_status = true, -- displays file status (readonly status, modified status)
      path = 1 -- 0 = just filename, 1 = relative path, 2 = absolute path
    } },
    lualine_x = { 'location' },
    lualine_y = {},
    lualine_z = {}
  },
  tabline = {},
  extensions = { 'fugitive' }
}
Enter fullscreen mode Exit fullscreen mode

Lspconfig

Neovim has a built-in LSP support.
You can easily configure it by using neovim/nvim-lspconfig.
For example, to enable typescript language server on Neovim:

local status, nvim_lsp = pcall(require, "lspconfig")
if (not status) then return end

local protocol = require('vim.lsp.protocol')

local on_attach = function(client, bufnr)
  -- format on save
  if client.server_capabilities.documentFormattingProvider then
    vim.api.nvim_create_autocmd("BufWritePre", {
      group = vim.api.nvim_create_augroup("Format", { clear = true }),
      buffer = bufnr,
      callback = function() vim.lsp.buf.formatting_seq_sync() end
    })
  end
end

-- TypeScript
nvim_lsp.tsserver.setup {
  on_attach = on_attach,
  filetypes = { "typescript", "typescriptreact", "typescript.tsx" },
  cmd = { "typescript-language-server", "--stdio" }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to install typescript language server itself:

npm i -g typescript-language-server
Enter fullscreen mode Exit fullscreen mode

Auto-completion: Lspkind and cmp

lspkind and cmp

To get LSP-aware auto-completion feature with fancy pictograms, I use the following plugins:

Configure it like so:

local status, cmp = pcall(require, "cmp")
if (not status) then return end
local lspkind = require 'lspkind'

cmp.setup({
  snippet = {
    expand = function(args)
      require('luasnip').lsp_expand(args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ['<C-d>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.close(),
    ['<CR>'] = cmp.mapping.confirm({
      behavior = cmp.ConfirmBehavior.Replace,
      select = true
    }),
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'buffer' },
  }),
  formatting = {
    format = lspkind.cmp_format({ with_text = false, maxwidth = 50 })
  }
})

vim.cmd [[
  set completeopt=menuone,noinsert,noselect
  highlight! default link CmpItemKind CmpItemMenuDefault
]]
Enter fullscreen mode Exit fullscreen mode

Syntax highlightings: Treesitter

treesitter

Treesitter is a popular language parser for syntax highlightings.
First, install it:

brew install tree-sitter
Enter fullscreen mode Exit fullscreen mode

Install nvim-treesitter/nvim-treesitter with Packer and configure it like so:

local status, ts = pcall(require, "nvim-treesitter.configs")
if (not status) then return end

ts.setup {
  highlight = {
    enable = true,
    disable = {},
  },
  indent = {
    enable = true,
    disable = {},
  },
  ensure_installed = {
    "tsx",
    "toml",
    "fish",
    "php",
    "json",
    "yaml",
    "swift",
    "css",
    "html",
    "lua"
  },
  autotag = {
    enable = true,
  },
}

local parser_config = require "nvim-treesitter.parsers".get_parser_configs()
parser_config.tsx.filetype_to_parsername = { "javascript", "typescript.tsx" }
Enter fullscreen mode Exit fullscreen mode

Autotag and Autopair

For React apps, you often want to close tags quickly.
windwp/nvim-ts-autotag is exactly what you want.

local status, autotag = pcall(require, "nvim-ts-autotag")
if (not status) then return end

autotag.setup({})
Enter fullscreen mode Exit fullscreen mode

windwp/nvim-autopairs is for closing brackets.

local status, autopairs = pcall(require, "nvim-autopairs")
if (not status) then return end

autopairs.setup({
  disable_filetype = { "TelescopePrompt" , "vim" },
})
Enter fullscreen mode Exit fullscreen mode

Fuzz finder: Telescope

telescope

telescope.nvimย provides an interactive fuzzy finder over lists, built on top of the latest Neovim features.
I also use telescope-file-browser.nvim as a filer.

Itโ€™s so useful because you can search files while viewing the content of the files without actually opening them. It supports various sources likeย Vim,ย files,ย Git,ย LSP, andย Treesitter. Check outย the showcaseย of Telescope.

Install kyazdani42/nvim-web-devicons to get file icons on Telescope, statusline, and other supported plugins.

The configuration would look like so:

local status, telescope = pcall(require, "telescope")
if (not status) then return end
local actions = require('telescope.actions')
local builtin = require("telescope.builtin")

local function telescope_buffer_dir()
  return vim.fn.expand('%:p:h')
end

local fb_actions = require "telescope".extensions.file_browser.actions

telescope.setup {
  defaults = {
    mappings = {
      n = {
        ["q"] = actions.close
      },
    },
  },
}

-- keymaps
vim.keymap.set('n', ';f',
  function()
    builtin.find_files({
      no_ignore = false,
      hidden = true
    })
  end)
vim.keymap.set('n', ';r', function()
  builtin.live_grep()
end)
vim.keymap.set('n', '\\\\', function()
  builtin.buffers()
end)
vim.keymap.set('n', ';t', function()
  builtin.help_tags()
end)
vim.keymap.set('n', ';;', function()
  builtin.resume()
end)
vim.keymap.set('n', ';e', function()
  builtin.diagnostics()
end)
Enter fullscreen mode Exit fullscreen mode

file browser

Use the telescope browser extension:

telescope.setup {
  defaults = {
    mappings = {
      n = {
        ["q"] = actions.close
      },
    },
  },
  extensions = {
    file_browser = {
      theme = "dropdown",
      -- disables netrw and use telescope-file-browser in its place
      hijack_netrw = true,
      mappings = {
        -- your custom insert mode mappings
        ["i"] = {
          ["<C-w>"] = function() vim.cmd('normal vbd') end,
        },
        ["n"] = {
          -- your custom normal mode mappings
          ["N"] = fb_actions.create,
          ["h"] = fb_actions.goto_parent_dir,
          ["/"] = function()
            vim.cmd('startinsert')
          end
        },
      },
    },
  },
}
telescope.load_extension("file_browser")

vim.keymap.set("n", "sf", function()
  telescope.extensions.file_browser.file_browser({
    path = "%:p:h",
    cwd = telescope_buffer_dir(),
    respect_gitignore = false,
    hidden = true,
    grouped = true,
    previewer = false,
    initial_mode = "normal",
    layout_config = { height = 40 }
  })
end)
Enter fullscreen mode Exit fullscreen mode

Tabs: Bufferline

bufferline

I use akinsho/nvim-bufferline.lua to get better looking of tabs.
Make some customizations to make it look better with Solarized theme:

local status, bufferline = pcall(require, "bufferline")
if (not status) then return end

bufferline.setup({
  options = {
    mode = "tabs",
    separator_style = 'slant',
    always_show_bufferline = false,
    show_buffer_close_icons = false,
    show_close_icon = false,
    color_icons = true
  },
  highlights = {
    separator = {
      guifg = '#073642',
      guibg = '#002b36',
    },
    separator_selected = {
      guifg = '#073642',
    },
    background = {
      guifg = '#657b83',
      guibg = '#002b36'
    },
    buffer_selected = {
      guifg = '#fdf6e3',
      gui = "bold",
    },
    fill = {
      guibg = '#073642'
    }
  },
})

vim.keymap.set('n', '<Tab>', '<Cmd>BufferLineCycleNext<CR>', {})
vim.keymap.set('n', '<S-Tab>', '<Cmd>BufferLineCyclePrev<CR>', {})
Enter fullscreen mode Exit fullscreen mode

LSP Uls: Lspsaga

lsp_finder

rename action

definition preview

glepnir/lspsaga.nvim is one of my favorite LSP plugins.
It provides beautiful UIs for various LSP-related features like hover doc, definition preview, and rename actions.
My configuration is simple:

local status, saga = pcall(require, "lspsaga")
if (not status) then return end

saga.init_lsp_saga {
  server_filetype_map = {
    typescript = 'typescript'
  }
}

local opts = { noremap = true, silent = true }
vim.keymap.set('n', '<C-j>', '<Cmd>Lspsaga diagnostic_jump_next<CR>', opts)
vim.keymap.set('n', 'K', '<Cmd>Lspsaga hover_doc<CR>', opts)
vim.keymap.set('n', 'gd', '<Cmd>Lspsaga lsp_finder<CR>', opts)
vim.keymap.set('i', '<C-k>', '<Cmd>Lspsaga signature_help<CR>', opts)
vim.keymap.set('n', 'gp', '<Cmd>Lspsaga preview_definition<CR>', opts)
vim.keymap.set('n', 'gr', '<Cmd>Lspsaga rename<CR>', opts)
Enter fullscreen mode Exit fullscreen mode

Code formatter: Prettier and null-ls

I heavily rely on Prettier to format TypeScript/JavaScript/CSS files.
Use jose-elias-alvarez/null-ls.nvim and MunifTanjim/prettier.nvim to accomplish that.

First, you need prettierd:

brew install prettierd
Enter fullscreen mode Exit fullscreen mode

Then, configure null-ls as following:

local status, null_ls = pcall(require, "null-ls")
if (not status) then return end

null_ls.setup({
  sources = {
    null_ls.builtins.diagnostics.eslint_d.with({
      diagnostics_format = '[eslint] #{m}\n(#{c})'
    }),
    null_ls.builtins.diagnostics.fish
  }
})
Enter fullscreen mode Exit fullscreen mode

Prettier:

local status, prettier = pcall(require, "prettier")
if (not status) then return end

prettier.setup {
  bin = 'prettierd',
  filetypes = {
    "css",
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "json",
    "scss",
    "less"
  }
}
Enter fullscreen mode Exit fullscreen mode

Git markers: gitsigns

gitsigns

lewis6991/gitsigns.nvim provides git decorations for current buffers.
It helps you know which lines are currently changed.
It works out of the box.

require('gitsigns').setup {}
Enter fullscreen mode Exit fullscreen mode

git

git

I often view the code on GitHub.
dinhhuy258/git.nvim helps open GitHub right from Neovim, and provides git blame view in split view, which are super handy.

local status, git = pcall(require, "git")
if (not status) then return end

git.setup({
  keymaps = {
    -- Open blame window
    blame = "<Leader>gb",
    -- Open file/folder in git repository
    browse = "<Leader>go",
  }
})
Enter fullscreen mode Exit fullscreen mode

LSP tool: mason

mason

If you need additional LSP support for specific libraries, you may need williamboman/mason.nvim and williamboman/mason-lspconfig.nvim.
I use them for getting Tailwind CSS language server to work on Neovim.

local status, mason = pcall(require, "mason")
if (not status) then return end
local status2, lspconfig = pcall(require, "mason-lspconfig")
if (not status2) then return end

mason.setup({

})

lspconfig.setup {
  ensure_installed = { "sumneko_lua", "tailwindcss" },
}
Enter fullscreen mode Exit fullscreen mode

Add lspconfig:

local nvim_lsp = require "lspconfig"
nvim_lsp.tailwindcss.setup {}
Enter fullscreen mode Exit fullscreen mode

Thatโ€™s pretty much it!
I hope itโ€™s helpful for improving your Neovim environment.

Follow me online

Top comments (16)

Collapse
crisoncode profile image
Cristian Estarlich

Hi Takuya, nice to see you here! when I saw the first screenshot I thought in you immediately, thanks for share all of your config!

Collapse
thedenisnikulin profile image
Denis

Hello Takuya, I love your coding videos, they're so satisfying and relaxing! Great setup by the way. Do you feel any inconveniences while working in neovim? I've been writing in neovim for a while, but eventually jumped to vscode because there were some little bugs that didn't make me completely happy with the whole process. Is your setup fully convenient for you?

Collapse
joshuanr5 profile image
Joshua Navarro

Hi Takyuta, nice blog and video I like it some much.

I'm new in vim world and I don't know how to make the lspconfig and cmp show autocompletation for my eslint config (eslintrc.json), for example en vscode if I write "no-con" the IDE shows me suggestion for "no-console" but in vim I can't replicate that feature. If someone with more knowledge in this world can help me I will really appreciate.

Thanks everyone!!

Collapse
samji3877 profile image
Samuel Benson

A fantastic read Takuya! Thanks for the update :)

Collapse
kikecr profile image
Jesรบs CR

Greetings, I wrote and did all the configuration steps as you put it in the video but not in macos but I did it with ubuntu, but the autotag for the html tags does not work for me, everything else turned out very well for me, I must be missing something in the configuration

Collapse
menard_codes profile image
Menard Maranan

Definitely gonna bookmark this. I'm a fan of your YouTube channel and man how I envy your Neovim setup, lol.
Thanks a lot for this one!

Collapse
takunda profile image
Takunda Madechangu

I don't want to see that thing in my life

Collapse
natescode profile image
Nathan Hedglin

Wow, so much good stuff. Bookmarked to help me get my Neovim setup optimized.

Thanks!

Collapse
entykey profile image
Nguyen Huu Anh Tuan

I know you're Japanese, I've been watching you on youtube for a long time ago , thanks for the knowlege โค๏ธ

Collapse
medlabs profile image
MedLabs

why using many LSP plugins ?

Collapse
mbaneshi profile image
Mehdi Baneshi

Great . Thanks for Sharing

Collapse
drio profile image
David Rio Deiros

Does anyone know what laptop stand he is using in the video?

Collapse
hummingbird24 profile image
HummingBird24

Is the tailwind lsp server causing lag for you. It's been causing a lot of problems for me as of late?

Collapse
azmy60 profile image
azmy60 • Edited on

Yeah. It's been discussed here btw github.com/neovim/neovim/issues/19...

I started using mason as @craftzdog suggested but the autocompletion didn't work, the diagnostic works though.

Collapse
allefdouglasdev profile image
AllefDouglasDev

Are you able to use Tailwind autocomplete with twin.macro?

Collapse
hummingbird24 profile image
HummingBird24

That can be done by passing settings when setting up tailwind

local nvim_lsp = require "lspconfig"
nvim_lsp.tailwindcss.setup {
settings = {
tailwindCSS = {
experimental = {
classRegex = {
'tw([^])',
'tw="([^"]
)',
'tw={"([^"}])',
'tw\.\w+([^]
)',
'tw\(.?\)([^])',
},
},
},
},

})

๐ŸŒš Friends don't let friends browse without dark mode.

Sorry, it's true.