Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Content] Add diogogpinto-filament-themes-full-guide.md content and respective art #587

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 333 additions & 0 deletions content/articles/diogogpinto-filament-themes-full-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
---
title: "Filament Custom Themes - The Full Guide"
slug: diogogpinto-filament-themes-full-guide
author_slug: diogogpinto
publish_date: 2024-29-10
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
categories: [panel-builder]
type_slug: article
---

Welcome to this "full guide" on how to create a custom theme in Filament v3.

Are you looking to elevate your Filament v3 Panels with a unique appearance? This in-depth guide will take you beyond the basics covered in the official documentation.

## Resources

I would recommend checking the following resources if you haven't already.

- [Filament Official Documentation](https://filamentphp.com/docs/)
- [Filament Official Documentation on Themes](https://filamentphp.com/docs/3.x/panels/themes#creating-a-custom-theme)
- [Filament Official Documentation on Style Customization](https://filamentphp.com/docs/3.x/support/style-customization)
- [Tailwind Official Documentation](https://tailwindcss.com/docs/installation)

## Before You Start

I am assuming you already have a Filament Panel setup. If you don't have one, please set up a panel using the [official documentation guide](https://filamentphp.com/docs/3.x/panels/getting-started).

## Creating a theme

In this example, we will be creating a custom theme for Filament's default panel, the `admin`panel.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

> [!note]
> The same steps apply for any other panel you may have in your Filament project

### Creating the theme using the terminal

In your project's root directory, run:

```bash
php artisan make:filament-theme
```

It will scan your app for all the panels you have installed and let you select which panel this themes applies to. Select the panel which you wish to customize. In this particular case, we will select the panel `admin`.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

![Create Theme Terminal Command](/images/content/articles/diogogpinto-filament-themes-full-guide/create-theme.webp)

After you've selected the panel, the terminal will give you all further instructions, but we'll go by each one step by step.

![After Creating the Theme](/images/content/articles/diogogpinto-filament-themes-full-guide/after-create-theme.webp)

#### Editing vite.config.js

Go to the `vite.config.js` file in your project's root directory and set the input array like below:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

```javascript
import { defineConfig } from 'vite'
import laravel, { refreshPaths } from 'laravel-vite-plugin'

export default defineConfig({
plugins: [
laravel.default({
input: [
'resources/css/app.css',
'resources/js/app.js',
// Add the following line of code
'resources/css/filament/admin/theme.css'
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
],
refresh: [
...refreshPaths,
'app/Filament/**',
'app/Forms/Components/**',
'app/Livewire/**',
'app/Infolists/Components/**',
'app/Providers/Filament/**',
'app/Tables/Columns/**',
],
}),
],
})
```

See how we've added `resources/css/filament/admin/theme.css` to that array? This will tell vite where our custom theme is located when running the build process.

>[!note]
> If you have other themes for other panels, you can add them in the same array. This vite config file can contain as many themes for as many panels as you like!

#### Registering the viteTheme() on our panel

Now we shall edit the `AdminPanelProvider.php` file and add the viteTheme method to our `$panel`, like the example below:

```php
return $panel
->id('admin')
->viteTheme('resources/css/filament/admin/theme.css') // Added this line of code
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
```

Our panel now knows we are using a custom theme and what our theme path is.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

#### Running the build process

![Build Process](/images/content/articles/diogogpinto-filament-themes-full-guide/npm-run-build.webp)

At last, we shall run the build process with the terminal in our project's root directory:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

```bash
npm run build
```

### Registering vendor files from Filament Plugins

Sometimes, when installing plugins like [Auth UI Enhancer](https://www.github.com/diogogpinto/filament-auth-ui-enhancer) or [Filament Curator](https://github.com/awcodes/filament-curator), for example, the docs may say something to the effect of:

> 1. Add the plugin's views to your tailwind.config.js file.
> ```javascript
> content: [
> './vendor/diogogpinto/filament-auth-ui-enhancer/resources/**/*.blade.php',
> ]
> ```

#### What does this mean?

We need to make sure that - when we start the building process - `vite` will check the files in the paths specified in the `content`array and collect all used CSS classes in the plugin views. It will then compile all used CSS classes into the one single CSS file in your Filament panel.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

So, in our case, installing `Auth UI Enhancer` and `Curator`plugin in our panel, we will go to `resources/css/filament/admin/tailwind.config.js`and edit it like the code below:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

```js
import preset from '../../../../vendor/filament/filament/tailwind.config.preset'

export default {
presets: [preset],
content: [
'./app/Filament/Clusters/Products/**/*.php',
'./resources/views/filament/clusters/products/**/*.blade.php',
'./vendor/filament/**/*.blade.php',
// Added the following lines of code
'./vendor/diogogpinto/filament-auth-ui-enhancer/resources/**/*.blade.php',
'./vendor/awcodes/filament-curator/resources/**/*.blade.php',
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
],
}
```

Additionally, in the case of the `Curator` plugin, the author requires you to import some CSS files in your `theme.css`. This can be done, by editing the file `resources/css/filament/admin/theme.css` and making it look like below:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

```css
@import '/vendor/filament/filament/resources/css/theme.css';
@import '/node_modules/cropperjs/dist/cropper.css';
@import '/vendor/awcodes/filament-curator/resources/css/plugin.css';
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

@config 'tailwind.config.js';
```

This last step imports all custom classes needed by the plugin to render correctly. It imports the class, puts into our panel's unique CSS file and minimizes it all for greater performance.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

#### Running the build process

When installing plugins and editing any theme related files, always remember to run `npm run build` when you're finished to compile all the assets.

## Customizing the theme

Laravel, and Filament being Laravel based, offer multiple solutions to the same problem. So, we will address two ways of building your own Filament look and feel, the recommended way and the not-so-recommended way. Let's start with the latter.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

### Before starting the customization process

You should start the `npm run dev` process in the root of your project. This process watches for changes in the files registered in the `vite.config.js` (and `tailwind.config.js` by extension) and automatically builds a temporary CSS file to render the changes on file save.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

> [!note]
> You still need to run `npm run build` when you're done customizing your theme
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

### The recommended way to customize your theme

The Filament panels core offers you a list of classes. Every component of the panel layout, including forms, infolists and tables, were wrapped in custom classes to make it possible, for any developer, to customize the panel look and feel. Let's look at the following example:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

![Breadcrumb Example](/images/content/articles/diogogpinto-filament-themes-full-guide/breadcrumb-example.webp)

Looking at the breadcrumb component with Chrome's builtin Developer Tools > Inspect Element, you can see that the `<nav>`na tag contains the following classes:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

```html
<nav class="fi-breadcrumbs mb-2 hidden sm:block">
```

The `.fi-breadcrumbs` class is inserted to allow developers to further optimize the breadcrumbs' look and feel. By default, the class has no CSS properties defined in itself, it's only there for the developer to apply custom styles.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

#### Customizing the CSS

Picking up in the above example of `.fi-breadcrumbs`, we can now apply custom CSS classes on our `theme.css`. You can either use Tailwind utility classes or just write vanilla CSS. Let's say you want to override the default margin `mb-2`. Inputting the below CSS will yield the exact same results:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

```css
.fi-breadcrumbs {
@apply mb-4;
}

.fi-breadcrumbs {
margin-bottom: 1rem;
}
Comment on lines +186 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think this isn't necessarily always going to work.

I believe that, specifically referencing the bottom margin, we won't reliably be able to override the CSS styling. This is because there is already a mb-2 class in the class string, so when our styles get applied via .fi-breadcrumbs, mb-2 and our mb-4 will (theoretically) have the same specificity. If that is the case, whichever class is defined further down in the cascade with take precedence.

I might be wrong here, but regardless it may be worth changing this example from an "overriding" example to an "adding" example. Something like adding a top padding, border, etc.

@zepfietje (or anyone from Core), let me know if I'm completely incorrect here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexandersix just wondering, given that the custom classes are always defined after TW classes, wouldn't they always take precedence? Or is there a case where that may not be true?

I may be missing something

```

As the `.fi-breadcrumbs` class is inserted before the default Tailwind utility classes, applying custom CSS will always override the default style.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above comment, but I believe the order in which a class string is laid out has no bearing on specificity or the cascade, so having multiple classes of the same specificity may lead to unreliable outputs based on where the Tailwind classes are defined in relation to one another.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, totally depends on specificity.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zepfietje @alexandersix

I'm probably missing something, given that both TW classes and custom classes created with @apply are technically single class selectors, shouldn’t they all have equal specificity?

I clearly made a mistake in the text, as it isn't order of declaration that matters, but the build process that builds custom classes after the utility classes.

Hope to get some clarity, thank you in advance!

Copy link
Author

@diogogpinto diogogpinto Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zepfietje hey Zep, sorry for pinging you on this, but just so I can ship the changes, in the text I'm considering saying something like:

As the custom styles for the .fi-breadcrumbs class are processed by Tailwind and typically appear later in the compiled CSS file, these styles will generally override conflicting Tailwind utility classes applied to the same elements. This is due to the CSS cascading order, where later rules take precedence when specificity is equal.

If you encounter style conflicts when using @apply and your custom styles are not overriding Tailwind utilities as expected, you have two options:

  1. Within your @apply statement, you can use Tailwind’s important modifier by adding an exclamation mark before the utility class name. For example:
.fi-breadcrumbs {
  @apply !mb-4;
}
  1. Alternatively, you can add the !important declaration if you opt for vanilla CSS:
.fi-breadcrumbs {
  margin-bottom: 1rem !important;
} 

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this makes sense?


#### Diving deeper

If we dive deeper into the breadcrumbs, we will find all the following classes, in this hierarchy:

```
- .fi-breadcrumbs
-- .fi-breadcrumbs-list
--- .fi-breadcrumbs-item
---- .fi-breadcrumbs-item-separator
---- .fi-breadcrumbs-item-label
```

Let's say we want to really change the breadcrumbs appearance. We can style each of this classes. Check the following example:

```css
.fi-breadcrumbs-list {
@apply gap-x-0;
}

.fi-breadcrumbs-item {
@apply gap-x-0;
}

.fi-breadcrumbs-item-label {
@apply font-thin dark:text-primary-500 text-primary-500;
}

span.fi-breadcrumbs-item-label {
@apply border-b-2 border-b-primary-500 font-semibold -mb-[2px];
}
Comment on lines +220 to +226
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is confusing as to why you have the same class twice, with one being more specific due to the span selector. Could you combine these two together?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexandersix I believe in this case breadcrumbs active item is wrapped in a <span> tag, the others on a <a> tag.

Maybe something like the following would be better?

.fi-breadcrumbs-list > li:last-child .fi-breadcrumbs-item-label

Will have to try it out.


.fi-breadcrumbs-item-separator {
@apply fill-primary-500 h-4;
}
```

![Breadcrumbs Final Example](/images/content/articles/diogogpinto-filament-themes-full-guide/breadcrumbs-final.webp)

There are a lot of classes around every Filament component. You can go through each one and apply your custom CSS, so you can create a unique design for your Panel.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

> [!TIP]
> There's a useful Filament Plugin ([Theme Inspector](https://filamentphp.com/plugins/codewithdennis-theme-inspector)) that lets you inspect all the classes in your panel in your dev environment. You can even copy to clipboard! It really smoothes the development process.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

### Going behind CSS
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

Sometimes there are some things your theme requires that go behind CSS. You may want to add a custom Livewire component, Blade View or JS script. As these modifications are outside the scope of this guide, I would advise you to check the following resources:
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

- [Render Hooks on the Official Docs](https://filamentphp.com/docs/3.x/support/render-hooks) - check how you can render custom views almost anywhere in your panels
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
- [Registering Assets](https://filamentphp.com/docs/3.x/support/assets#registering-javascript-files) - check how you can register custom JS/CSS into your Filament panels
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

### The ~wrong~ unrecommended way to customize your theme
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should not have this section in my opinion as this approach should basically never be used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm on the fence about this one. On the one hand, I completely agree that we almost never want this approach to be used. However, on the other hand, by having this here, we're specifically calling out the fact that this is the wrong approach, dissuading people from using it.

I guess it comes down to whether or not this is going to prompt more people to remember that this is an option and use it or if it's going to prevent people from using it because it's explicitly stated not to.

I think I come down more on the side of leave it in to ensure that people know it's the wrong way to do things. My hunch is that the people reading this article are (in general) going to be deep enough in Filament to know that overriding views is an option in Laravel 🤷🏻

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see both points!

I inserted because sometimes you may want to change specific functionality around whole components that may not necessarily break functionality on updates. My case was the topbar, but later I disabled it and used render hooks. But that approach gave me some headaches around mobile and had to create a whole new mobile trigger for the sidebar - if I had to do it all over again, I would publish the file and monitor each release for breaking changes.


Another way of doing things, is publishing all the views of the Filament panels package. Why isn't this recommended? The docs say exactly why:

> You may be tempted to publish the internal Blade views to your application so that you can customize them. We don't recommend this, as it will introduce breaking changes into your application in future updates. Please use the CSS hook classes wherever possible.
> If you do decide to publish the Blade views, please lock all Filament packages to a specific version in your composer.json file, and then update Filament manually by bumping this number, testing your entire application after each update. This will help you identify breaking changes safely.

> [!warning]
> This is for demonstration purposes only, you really shouldn't do this as it can break your panels in future releases/updates. If you need to do this, only publish the necessary files and lock filament to your project's current version

So, if you were to do this, you could run the following command in your terminal:

```bash
php artisan vendor:publish --tag=filament-panels-views
```

Now, inside your `resources/views/vendor/filament-panels` folder are a bunch of files - these are the original view files you can (but shouldn't) customize.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

![Vendor Files](/images/content/articles/diogogpinto-filament-themes-full-guide/vendor-publish.webp)

Navigate to your themes `tailwind.config.js` and add the filament-panels folder to the content array, so vite can process these file changes. If you're following along, your file should look like this:

```js
import preset from '../../../../vendor/filament/filament/tailwind.config.preset'

export default {
presets: [preset],
content: [
'./app/Filament/Clusters/Products/**/*.php',
'./resources/views/filament/clusters/products/**/*.blade.php',
'./vendor/filament/**/*.blade.php',
'./vendor/diogogpinto/filament-auth-ui-enhancer/resources/**/*.blade.php',
'./vendor/awcodes/filament-curator/resources/**/*.blade.php',
// Added the following line of code
'./resources/views/filament-panels/**/*.blade.php',
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved
],
}
```

#### Example - make the topbar backdrop blur

If you're editing the blade files directly, to blur the background of a topbar, you should open the following file:

```
./resources/views/vendor/filament-panels/components/topbar/index.php
```

Now we can edit the `<nav>` tag, and make it look like the following:

```html
<nav
class="flex h-16 items-center gap-x-4 bg-white bg-opacity-50 backdrop-blur-lg px-4 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:bg-opacity-50 dark:ring-white/10 md:px-6 lg:px-8"
>
```

To make it look consistent in the sidebar, we should also open the following file:

```
./resources/views/vendor/filament-panels/components/sidebar/index.php
```

And change the `<header>` tag to make it look like:

```html
<header
class="fi-sidebar-header flex h-16 items-center bg-white bg-opacity-50 backdrop-blur-lg px-6 ring-1 ring-gray-950/5 dark:bg-gray-900 dark:bg-opacity-50 dark:ring-white/10 lg:shadow-sm"
>
```

And there it is. We were able to customize the theme file in the unrecommended way. We shouldn't really do this, unless we want to add complex logic to the code. If this is the case, you can delete all the files except the ones you are directly going to modify.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

![Blade editing adding backdrop](/images/content/articles/diogogpinto-filament-themes-full-guide/blade-backdrop-blur.webp)

> [!note]
> All the changes you make to these files will render on ALL of your Filament panels in the project, unless you use some kind of conditional statements in your blade files.

Remember to the things the right way whenever possible.
diogogpinto marked this conversation as resolved.
Show resolved Hide resolved

## Support

If you want support on creating a theme, I would recommend the following channels:

- [Filament Github Discussions](https://github.com/filamentphp/filament/discussions)
- [Filament Official Discord](https://filamentphp.com/discord)

You can also reach me on Discord or [Twitter](https://www.twitter.com/diogogpinto) if you have any questions regarding this particular guide and have any doubts regarding this process.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading