Skip to content
This repository has been archived by the owner on Oct 11, 2023. It is now read-only.

Extensible Design

Cody Mullins edited this page Jun 3, 2022 · 16 revisions

Goal

Provide a default theme, but still allow customization with Tailwind.

Use Cases

  1. Use all components out of the box with no customizations required

this use case is fairly straightforward and should not require much outside of choosing a balanced, accessible color scheme.

  1. Use all components with minor customizations and overrides

this use case is slightly more complicated, due to the question - how can someone easily tweak one or two Tailwind classes without completely re-theming the entire component?

  1. Use all components with a choice of slightly varied themes based around the core style

this use case may require a dictionary/record type lookup or using @apply in Tailwind

  1. Use all components for logic and functionality with an entirely new theme

same as #3

Existing Patterns

To modify themes, structures, and sizes many libraries provide an enum. What needs to be determined is,

  1. Does this pattern work with extensibility in Tailwind?
  2. If so, how would this look to a user of the library?
public enum Color 
{
  Primary,
  Secondary,
  // etc
}

For sizing, many libraries provide an enum such as Size

public enum Size
{
  Small,
  Medium,
  Large,
}

Design Choices

Option 1: Use @apply to create custom classes

  • Use @apply to combine Tailwind classes into custom CSS
  • Add a [Parameter] that allows the caller of the component to pass in their own Tailwind classes, effectively overriding the built-in style

Example

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-default {
    @apply inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0;
  }
}
<button class="@Tailwind">@ChildContent</button>

@code {
    [Parameter]
    public string Tailwind { get; set; } = "btn-default";

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private string GetTailwind()
    {
        switch (ButtonType) 
        {
            case ButtonType.Default 
            {
                return "btn-default";
            }
            // ... 
        }
    }
}
<!-- No style changes -->
<MakaniButton>Default Button</MakaniButton>

<!-- Overwrites all default styles -->
<MakaniButton Tailwind="bg-gray-700">Customized Button</MakaniButton>

<!-- Adds only bg-gray-600 -->
<MakaniButton Css="bg-gray-600">Default Button with Additional Styling</MakaniButton>

Pros

  • Provides default styling to components
  • Easy adoption, familiar pattern for those used to other libraries such as Bootstrap

Cons

  • Significantly changing styling may require copying styles. could be ok
  • See note below from Tailwind Documentation.

Use Tailwind’s utilities directly in your markup the way they are intended to be used, and don’t abuse the @apply feature to do things like this > and you will have a much better experience.

Tailwind docs for @apply

Option 2: Option 1 + support overriding @apply styles with user's @apply files

Option 3: Add Tailwind classes, no @apply

Example

<button class="@Tailwind">@ChildContent</button>

@code {
    [Parameter]
    public ButtonType ButtonType { get; set; } = ButtonType.Default;

    [Parameter]
    public string Tailwind { get; set; } = GetTailwind();

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private string GetTailwind()
    {
        switch (ButtonType) 
        {
            case ButtonType.Default 
            {
                return "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0";
            }
            // ... 
        }
    }
}
<!-- No style changes -->
<MakaniButton>Default Button</MakaniButton>

<!-- Overwrites all default styles -->
<MakaniButton Tailwind="bg-gray-700">Customized Button</MakaniButton>

<!-- Adds only bg-gray-600 -->
<MakaniButton Css="bg-gray-600">Default Button with Additional Styling</MakaniButton>

Pros

  • Less complexity with Makani project structure
  • Smaller CSS bundle
  • Easier to debug and modify in dev tools

Cons

  • Potentially harder for community to customize?
  • Harder to debug