Soft Customization
The soft customization.
The soft customization involves customizing components at runtime, either through a service provider like AppServiceProvider or object classes. The idea behind soft customization is to explore the building blocks of customization for each component. Even if you are starting with Laravel, with a little attention to the docs below, you will be able to fully customize the components using this concept.
Since the idea of soft customization is to apply customization through PHP object classes, the first thing you
need to do is make sure that TailwindCSS tracks the classes that will be defined from your application's *.php
files.
To do this, you need to edit your app.css
CSS file by inserting this content:
@source '../../app/Providers/*.php';
Now that you have prepared TailwindCSS to track your custom classes, let's start customizing your components. Let's take a look at an example:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { // ... TallStackUi::customize() ->form('input') ->block('input.base', 'w-full rounded-full'); // or... TallStackUi::customize('form.input') ->block('input.base', 'w-full rounded-full'); }}
In this example we are touching and replacing all the classes in the input.base
block of the input
component with the content: w-full rounded-full
.
This means that every input component displayed on the application pages will have these classes, instead of the
original component classes.
At this point you may be wondering how to "discover" the blocks of each component. To do this, when
browsing the documentation of each component individually you will notice a button called
Customize: {Component Name}
which, when clicked, will display a modal containing
all the blocks - and their names, as well as the original classes that are defined by each block, for example:
Form Checkbox, Customization Blocks
Example:
// AppServiceProvider, "boot" method. TallStackUi::customize() ->form('checkbox') ->block('block', 'classes');
Since soft customization was created to be easy to use, just like Pest,
the soft customization offers a concept of fluency when using the and
like a property or method.
The idea behind this approach is to customize more than one component at the same time.
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { // 1. Property TallStackUi::customize() ->form('input') ->block('input.base', 'w-full rounded-full') ->and ->avatar() ->block('wrapper.sizes.sm', 'w-8 h-8 text-xs') // Or, 2. Method TallStackUi::customize() ->form('input') ->block('input.base', 'w-full rounded-full') ->and() ->avatar() ->block('wrapper.sizes.sm', 'w-8 h-8 text-xs') }}
Since a component has several blocks that organize the classes applied to the component, you can customize one block at a time or all of them at once:
use TallStackUi\Facades\TallStackUi;use App\TallStackUi\InputCustomization; class AppServiceProvider extends ServiceProvider{ public function boot(): void { TallStackUi::customize() ->form('input') ->block('input.base', new InputCustomization()) ->block('icon.wrapper', fn (array $data) => 'px-4 py-2') ->block('icon.paddings.left', 'pl-10'); // Or ... TallStackUi::customize() ->form('input') ->block([ 'input.class' => new InputCustomization(), 'icon.wrapper' => fn (array $data) => 'px-4 py-2', 'icon.paddings.left' => 'pl-10', ]); }}
You may have noticed that in the example above we used the InputPersonalization
class.
This is a simple invokable object class, because soft customization also allows you to make your customization into object invokable
classes. This approach is ideal if you are someone who prioritizes organization above all else. Let's take a look at an example:
Preparing:
use TallStackUi\Facades\TallStackUi;use App\TallStackUi\InputCustomization; class AppServiceProvider extends ServiceProvider{ public function boot(): void { TallStackUi::customize() ->form('input') ->block('input.base', new InputCustomization()); }}
Customizing:
// You must track this namespace in the TailwindCSS config file!namespace App\TallStackUi; class InputCustomization{ public function __invoke(array $data): string { return 'w-full rounded-full'; }}
You may have noticed that the example above there is a variable called $data
. This variable is an array
containing all the component's properties, including the values passed when you used the component somewhere in your application.
Using the input
like this:
<x-input label="Name" hint="Your full name" />
The $data
will be something like:
[ "label" => "Name" "hint" => "Your full name" "icon" => null "clearable" => null "invalidate" => null "position" => "left" "prefix" => null "suffix" => null "componentName" => "input" "attributes" => Illuminate\View\ComponentAttributeBag {...} "blade" => Illuminate\View\InvokableComponentVariable {...} "customization" => Illuminate\View\InvokableComponentVariable {...} "ignoredParameterNames" => Illuminate\View\InvokableComponentVariable {...} "classes" => TallStackUi\View\Components\Form\Input::classes(?Closure $callback = null): [...] "slot" => Illuminate\View\ComponentSlot {...} "__laravel_slots" => [...] "livewire" => true "property" => null "error" => false "id" => null]
block
method, this is a way of doing a complete replacement of the original component classes
by the blocks, an expected behavior when the soft customization was created. Luckily we have four special helpers to interact
with the original classes by touching their content but preserving everything else. Let's take a look at an example:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { TallStackUi::customize() ->form('input') ->block('input.base') ->replace('rounded-md', 'rounded-full'); // Or... TallStackUi::customize() ->form('input') ->block('input.base') ->replace([ 'rounded-md' => 'rounded-full', 'border-0' => 'border-1', ]); }}
Note that in the example above we omitted the second parameter of the block
method,
this way we can access four useful methods that allow us to touch the component's original
classes in an easy way in order to make modifications while maintaining the rest of the original content.
All the four methods:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { TallStackUi::customize() ->form('input') ->block('input.base') // Replace: replace parts of the original content. // Accepts: // - single: from/to replace, // - array for multiples replaces in the first parameter ->replace('rounded-md', 'rounded-full'); TallStackUi::customize() ->form('input') ->block('input.base') // Remove: // - single removal // - an array for multiple removals ->remove('w-full'); TallStackUi::customize() ->form('input') ->block('input.base') // Append: appends classes as string ->append('px-4'); TallStackUi::customize() ->form('input') ->block('input.base') // Prepend: prepend classes as string ->prepend('py-4'); }}
Now that these methods have been introduced, let's imagine that you want to transform all your inputs into a fully round style to follow the look of your application, so all the work (🥵) you need to do is:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { TallStackUi::customize() ->form('input') ->block('input.base') ->replace('rounded-md', 'rounded-full'); }}
While soft customization is powerful and easy to use, there is a catch: all soft customization are applied to all components, and you cannot assign specific customization to a component only once. However, just like in VueJS, where we have scoped CSS - CSS applied only to the component that defined the scope, soft customization offers the same concept of scoped customization - customization that will only be applied to the components that have the scope defined. Let's take a look at an example:
This is a normal Alert component
This is a fully round Alert component
Notice how one alert is normal while the other is fully rounded? This was only possible thanks to scoped soft customization, which instead of turning all alerts into rounded alerts, turned only the one that was defined with the circle scope. Now let's see how to achieve the same result as in the example above:
First, let's do the same soft customization via service provider:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { TallStackUi::customize('alert') ->scope('circle') ->block('wrapper') ->replace('rounded-lg', 'rounded-full'); // Or ... TallStackUi::customize(component: 'alert', scope: 'circle') ->block('wrapper') ->replace('rounded-lg', 'rounded-full'); }}
The difference is that we must instruct that customization to be applied to a scope - defined by a unique name, and as you can see above, there are two ways to define the scope name. Just choose one of them and use it as you wish.
You have three different ways to define scopes:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { // 1 TallStackUi::customize('alert') ->scope('circle') ->block('wrapper') ->replace('rounded-lg', 'rounded-full'); // Or TallStackUi::customize(component: 'alert', scope: 'circle') ->block('wrapper') ->replace('rounded-lg', 'rounded-full'); // Or TallStackUi::customize() ->scope('circle') ->alert() ->block('wrapper') ->replace('rounded-lg', 'rounded-full'); }}
Lastly and most importantly, we must apply the use of the scope to the components that are alert
and that we want to receive the effects of the defined customization:
<x-alert text="This is a fully round Alert component" scope="circle" />
You can not set more than one scope in the same component.
Some TallStackUI components use other TallStackUI components internally. For example, the Color Picker
renders an Input, and the Date Picker renders a Floating panel. With internal scoped customization, you can
target and customize these internal instances without publishing Blade templates. The second parameter of the
customize
method accepts the scope name of the internal component:
use TallStackUi\Facades\TallStackUi; class AppServiceProvider extends ServiceProvider{ public function boot(): void { // Customize the input used inside the color picker TallStackUi::customize(component: 'input', scope: 'form.color.input') ->block('input.base', 'rounded-full'); // Customize the floating used inside the date picker TallStackUi::customize(component: 'floating', scope: 'form.date.floating') ->block('wrapper', 'shadow-2xl'); // Customize the badge used inside sidebar items TallStackUi::customize(component: 'badge', scope: 'sidebar.item.badge') ->block('wrapper.class', 'border-0'); }}
Below is the full reference of available scopes organized by parent component.
Wrapper Components
These scopes affect all form components that use the shared wrapper infrastructure.
| Parent | Child | Scope |
|---|---|---|
| wrapper.input | label |
wrapper.input.label
|
| wrapper.input | hint |
wrapper.input.hint
|
| wrapper.input | error |
wrapper.input.error
|
| wrapper.radio | error |
wrapper.radio.error
|
Form Components
| Component | Child | Scope |
|---|---|---|
| Color | input |
form.color.input
|
| Color | floating |
form.color.floating
|
| Password | input |
form.password.input
|
| Password | floating |
form.password.floating
|
| Currency | input |
form.currency.input
|
| Date | input |
form.date.input
|
| Date | floating |
form.date.floating
|
| Time | input |
form.time.input
|
| Time | floating |
form.time.floating
|
| Time | button |
form.time.button
|
| Upload | input |
form.upload.input
|
| Upload | label |
form.upload.label
|
| Upload | floating |
form.upload.floating
|
| Upload | error |
form.upload.error
|
| Select Styled | label |
form.select-styled.label
|
| Select Styled | input |
form.select-styled.input
|
| Select Styled | floating |
form.select-styled.floating
|
| Select Styled | hint |
form.select-styled.hint
|
| Select Styled | error |
form.select-styled.error
|
| Select Native | label |
form.select-native.label
|
| Select Native | hint |
form.select-native.hint
|
| Select Native | error |
form.select-native.error
|
| Pin | label |
form.pin.label
|
| Pin | hint |
form.pin.hint
|
| Pin | error |
form.pin.error
|
UI Components
| Component | Child | Scope |
|---|---|---|
| Table | select.styled |
table.select-styled
|
| Table | input |
table.input
|
| Table | checkbox |
table.checkbox
|
| Dialog | button (cancel) |
dialog.button
|
| Dropdown | floating |
dropdown.floating
|
| Dropdown Submenu | floating |
dropdown.submenu.floating
|
| Clipboard | label |
clipboard.label
|
| Clipboard | hint |
clipboard.hint
|
| Sidebar Item | badge |
sidebar.item.badge
|