Create: 2025-07-10, Update: 2025-07-10
This has been some time since I started to use the go templ. I would like to use tailwindcss with it, but never be able to configure Emacs to use the tailwindcss lsp together with templ, just as I stated in an old post.
I recently started to use daisyui, which is a tailwind component library, the need for a real lsp auto-completion is much needed now.
I decided to give all the existing solution for one more try.
Eglot just does not work. I am a long time Eglot user, now it even becomes a built-in package in Emacs. But until now, it still does not support multiple lsp server in a buffer, see the discussion started from 2020.
Since I am not yet motivated enough to write a multiplexer from scratch myself, (I doubted that whether I could do that), Eglot is out of the question now.
After all those improvements claimed by lsp-mode over the years, I gave it a try, again.
Now, I can confidently state that, lsp-mode is utter garbage. Ridiculous to configure, hard to apply settings, slow to show completions.
Last year, I thought it was my problem. Today, I can say lsp-mode is such a fucked up piece of software.
After years of development, lsp-bridge is becoming much promising.
Although I still don't like the python nature of the package, I think writing it in some compiled language and uses an executable would be a much cleaner solution. But for speed, lsp-bridge is second to none in Emacs lsp realm.
Configuring it is kind of simple. No more elisp list to json conversion nightmare.
I can proudly that I made tailwindCSS work in templ-ts-mode, for the first time since my last post.
npm is a must, although this is not some bloated JavaScript project.
Install tailwindcss and daisyui via npm in the project root.
npm install tailwindcss@latest @tailwindcss/cli@latest daisyui@latest
Since I am using tailwindcss v4, the tailwind.config.js is no longer the correct configuration, now tailwindcss uses an input.css file. So create an input.css file in project root.
@import "tailwindcss";
@source "./**/*.templ";
@plugin "daisyui";
Install tailwindcsss-language-server globally.
npm install -g @tailwindcss/language-server
lsp-bridge is a bit tricky to install, since it requires some python packages to be installed, your package manager is very likely to stop you from messing up the machine python packages. Therefore, we need a virtual environment.
python -m venv ~/.venv
. ~/.venv/bin/activate
Install the required packages stated in lsp-bridge.
Install pyvenv package in Emacs, activate it in my Emacs session. (If you are a python developer, don't do this, you understand virtual environment more than me.)
(elpaca pyvenv
(require 'pyvenv)
(pyvenv-mode 1)
(pyvenv-activate "~/.venv"))
Install lsp-bridge
(elpaca
(lsp-bridge :host github :repo "manateelazycat/lsp-bridge" :files (:defaults "*.el" "*.py" "acm" "core" "langserver" "multiserver" "resources") :build (:not compile))
(require 'lsp-bridge)
(setq lsp-bridge-user-langserver-dir
(expand-file-name "langserver" user-emacs-directory))
(add-to-list 'lsp-bridge-default-mode-hooks 'templ-ts-mode-hook)
(setq lsp-bridge-user-multiserver-dir
(expand-file-name "multiserver" user-emacs-directory))
(add-to-list 'lsp-bridge-multi-lang-server-mode-list
'((templ-ts-mode) . "templ_tailwindcss2"))
(add-hook 'lsp-bridge-mode-hook (lambda () (corfu-mode -1)))
(add-hook 'templ-ts-mode-hook #'lsp-bridge-mode)
)
Make two directories in your user-emacs-directory
, mine is at ~/.config/emacs/
cd ~/.config/emacs
mkdir langserver multiserver
In langserver, we make two json, tailwindcss2.json
and templ.json
.
The reason it is tailwindcss2 is because lsp-bridge has a built-in tailwindcss config which we don't want to mess with. We need to tell tailwindcss lsp to treat templ file as html file.
In tailwindcss.json
, this config is stated in official templ guide.
{
"name": "tailwindcss2",
"languageId": "",
"command": ["tailwindcss-language-server", "--stdio"],
"settings": {
"tailwindCSS": {
"includeLanguages": {
"templ": "html"
}
}
},
"support-single-file": false
}
In templ.json
{
"name": "templ",
"languageId": "templ",
"command": ["templ", "lsp"],
"settings": {}
}
Next, in multiserver directory, create templ. We only interested in completion.
{
"completion": ["tailwindcss2", "templ"],
"completion_item_resolve": ["tailwindcss2", "templ"],
"diagnostics": ["templ"],
"code_action": ["templ"],
"execute_command": ["templ"],
"find_define": "templ",
"find_type_define": "templ",
"find_implementation": "templ",
"find_references": "templ",
"peek_find_definition": "templ",
"peek_find_references": "templ",
"formatting": "templ",
"hover": "templ",
"signature_help": "templ",
"prepare_rename": "templ",
"rename": "templ",
"document_symbol": "templ",
"workspace_symbol": "templ",
"semantic_tokens": "templ",
"inlay_hint": "templ"
}
That is it. You can open a templ file in your project and have completion suggestions by tailwindcss lsp.
The issue bother me for more than a year finally come to an end.
I seeked for help in reddit, and didn't receive much.
Just as I was thinking things are going well, a weird bug knock me right back to hell.
The completion by tailwindcss lsp did work, but very limited.
It will only provide suggestion when you are in an unfinished tag. Let say the dollar sign is the cursor position.
// This work
templ bar() {
<div class="$"
}
// This works too
templ bar() {
<div class="$"><
}
// This works also
templ bar() {
<div class="$"></d
}
// This is fuck up
templ bar() {
<div class="$"></div>
}
// This work
templ bar() {
<div>
<div class="$">
</div>
}
// This work
templ bar() {
<div class="$">
<div></div>
}
// This fail
templ bar() {
<h1>
<div>
<div class="$"></div>
</div>
}
What kind of fucked up programming induced such a disgusting bug? Well, I went back to Eglot and continue to use my cape-dict based tailwindcss completion with corfu. Fuck tailwindcss lsp.
And thanks for Grok, generating a list of tailwindcss and daisyui classes is very easy now.