プラグインの作成

再利用可能なサードパーティのプラグインを使用してTailwindを拡張する。


Overview

プラグインを使用すると、CSSの代わりにJavaScriptを使用して、Tailwindの新しいスタイルを登録してユーザーのスタイルシートに挿入できます。

最初のプラグインを開始するには、Tailwindのplugin関数をtailwindcss/pluginからインポートし、設定ファイルのpluginsリストの最初の引数として無名関数を使用して呼び出します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addUtilities, addComponents, e, prefix, config }) {
      // Add your custom styles here
    }),
  ]
}

プラグイン関数は、いくつかのヘルパー関数に分解できる単一のオブジェクト引数を受け取ります。

  • addUtilities() 新しいユーティリティスタイルを登録する
  • addComponents() 新しいコンポーネントスタイルを登録する
  • addBase() 新しい基本スタイルを登録する
  • addVariant() カスタムバリアントを登録する
  • e() クラス名で使用されることを意図した文字列をエスケープする
  • prefix() ユーザーが構成したプレフィックスをセレクターの一部に手動で適用する
  • theme() ユーザーのテーマ構成で値を検索する
  • variants() ユーザーのバリアント構成で値を検索する
  • config() ユーザーのTailwind構成で値を検索する
  • postcss PostCSSディレクトリで直接低レベルの操作を行うため

Adding utilities

addUtilities関数を使用すると、@tailwind utilitiesディレクティブで(組み込みユーティリティとともに)出力される新しいスタイルを登録できます。

プラグインユーティリティは、組み込みユーティリティのに、登録された順序で出力されます。

そのため、プラグインが組み込みユーティリティと同じプロパティのいずれかを対象としている場合、プラグインユーティリティが優先されます。

プラグインから新しいユーティリティを追加するには、addUtilitiesを呼び出し、CSS-in-JS構文を使用してスタイルを渡します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addUtilities }) {
      const newUtilities = {
        '.skew-10deg': {
          transform: 'skewY(-10deg)',
        },
        '.skew-15deg': {
          transform: 'skewY(-15deg)',
        },
      }

      addUtilities(newUtilities)
    })
  ]
}

Prefix and important preferences

デフォルトでは、プラグインユーティリティは、ユーザーのprefixおよびʻimportant`設定を自動的に尊重します。

つまり、このTailwind構成が与えられた場合:

// tailwind.config.js
module.exports = {
  prefix: 'tw-',
  important: true,
  // ...
}

上記のプラグインの例では、次のCSSが生成されます。

.tw-skew-10deg {
  transform: skewY(-10deg) !important;
}
.tw-skew-15deg {
  transform: skewY(-15deg) !important;
}

必要に応じて、オプションオブジェクトを2番目のパラメータとしてaddUtilitiesに渡すことで、この動作をオプトアウトできます。 If necessary, you can opt out of this behavior by passing an options object as a second parameter to addUtilities:

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addUtilities }) {
      const newUtilities = {
        // ...
      }

      addUtilities(newUtilities, {
        respectPrefix: false,
        respectImportant: false,
      })
    })
  ]
}

Responsive and pseudo-class variants

レスポンシブ、ホバー、フォーカス、アクティブ、またはその他のスタイルのバリアントを生成するには、バリアントオプションを使用して生成するバリアントを指定します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addUtilities }) {
      const newUtilities = {
        // ...
      }

      addUtilities(newUtilities, {
        variants: ['responsive', 'hover'],
      })
    })
  ]
}

バリアントを指定するだけで、デフォルトのプレフィックスや重要なオプションをオプトアウトする必要がない場合は、バリアントの配列を2番目のパラメーターとして直接渡すこともできます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addUtilities }) {
      const newUtilities = {
        // ...
      }

      addUtilities(newUtilities, ['responsive', 'hover'])
    })
  ]
}

ユーザーがtailwind.config.jsファイルのvariantsセクションでバリアント自体を提供するようにしたい場合は、variants()関数を使用して構成済みのバリアントを取得できます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  variants: {
    customPlugin: ['responsive', 'hover'],
  },
  plugins: [
    plugin(function({ addUtilities, variants }) {
      const newUtilities = {
        // ...
      }

      addUtilities(newUtilities, variants('customPlugin'))
    })
  ]
}

Adding components

addComponents関数を使用すると、@tailwind componentsディレクティブで出力される新しいスタイルを登録できます。

これを使用して、ボタン、フォームコントロール、アラートや、他のフレームワークでよく見られる、ユーティリティクラスでオーバーライドする必要のあるビルド済みコンポーネントなど、より独善的な複雑なクラスを追加します

プラグインから新しいコンポーネントスタイルを追加するには、addComponentsを呼び出し、CSS-in-JS構文を使用してスタイルを渡します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents }) {
      const buttons = {
        '.btn': {
          padding: '.5rem 1rem',
          borderRadius: '.25rem',
          fontWeight: '600',
        },
        '.btn-blue': {
          backgroundColor: '#3490dc',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#2779bd'
          },
        },
        '.btn-red': {
          backgroundColor: '#e3342f',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#cc1f1a'
          },
        },
      }

      addComponents(buttons)
    })
  ]
}

Prefix and important preferences

デフォルトでは、コンポーネントクラスはユーザーのprefix設定を自動的に尊重します。 ただし、ユーザーの「重要な」設定の影響を受けません

つまり、このTailwind構成が与えられた場合:

// tailwind.config.js
module.exports = {
  prefix: 'tw-',
  important: true,
  // ...
}

上記のプラグインの例では、次のCSSが生成されます。

.tw-btn {
  padding: .5rem 1rem;
  border-radius: .25rem;
  font-weight: 600;
}
.tw-btn-blue {
  background-color: #3490dc;
  color: #fff;
}
.tw-btn-blue:hover {
  background-color: #2779bd;
}
.tw-btn-blue {
  background-color: #e3342f;
  color: #fff;
}
.tw-btn-blue:hover {
  background-color: #cc1f1a;
}

コンポーネント宣言を重要にする正当な理由はめったにありませんが、本当にそれを行う必要がある場合は、いつでも手動で!importantを追加できます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents }) {
      const buttons = {
        '.btn': {
          padding: '.5rem 1rem !important',
          borderRadius: '.25rem !important',
          fontWeight: '600 !important',
        },
        // ...
      }

      addComponents(buttons)
    })
  ]
}

セレクター内のすべてのクラスにはデフォルトでプレフィックスが付けられるため、次のようなより複雑なスタイルを追加すると:

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  prefix: 'tw-',
  plugins: [
    plugin(function({ addComponents }) {
      const components = {
        // ...
        '.navbar-inverse a.nav-link': {
            color: '#fff',
        }
      }

      addComponents(components)
    })
  ]
}

次のCSSが生成されます。

.tw-navbar-inverse a.tw-nav-link {
    color: #fff;
}

プレフィックスをオプトアウトするには、オプションオブジェクトを2番目のパラメータとしてaddComponentsに渡します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  prefix: 'tw-',
  plugins: [
    plugin(function({ addComponents }) {
      const components = {
        // ...
      }

      addComponents(components, {
        respectPrefix: false
      })
    })
  ]
}

Responsive and pseudo-class variants

コンポーネントのレスポンシブ、ホバー、フォーカス、アクティブ、またはその他のバリアントを生成するには、variantオプションを使用して生成するバリアントを指定します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents }) {
      const newComponents = {
        // ...
      }

      addComponents(newComponents, {
        variants: ['responsive', 'hover'],
      })
    })
  ]
}

バリアントを指定するだけで、デフォルトのプレフィックスや重要なオプションをオプトアウトする必要がない場合は、バリアントの配列を2番目のパラメーターとして直接渡すこともできます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents }) {
      const newComponents = {
        // ...
      }

      addComponents(newComponents, ['responsive', 'hover'])
    })
  ]
}

ユーザーがtailwind.config.jsファイルのvariantsセクションでバリアント自体を提供するようにしたい場合は、variants()関数を使用して構成済みのバリアントを取得できます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  variants: {
    customPlugin: ['responsive', 'hover'],
  },
  plugins: [
    plugin(function({ addComponents, variants }) {
      const newComponents = {
        // ...
      }

      addComponents(newComponents, variants('customPlugin'))
    })
  ]
}

Adding base styles

addBase関数を使用すると、@tailwind baseディレクティブで出力される新しいスタイルを登録できます。

これを使用して、基本的なタイポグラフィスタイル、独善的なグローバルリセット、または@font-faceルールなどを追加します。

プラグインから新しい基本スタイルを追加するには、addBaseを呼び出し、CSS-in-JS構文を使用してスタイルを渡します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addBase, config }) {
      addBase({
        'h1': { fontSize: config('theme.fontSize.2xl') },
        'h2': { fontSize: config('theme.fontSize.xl') },
        'h3': { fontSize: config('theme.fontSize.lg') },
      })
    })
  ]
}

基本スタイルは、divh1などのベアセレクターをターゲットにすることを目的としているため、ユーザーのprefixまたはimportant構成を尊重しません。


Escaping class names

プラグインがユーザー提供の文字列を含むクラスを生成する場合は、e関数を使用してそれらのクラス名をエスケープし、非標準文字が自動的に適切に処理されるようにすることができます。

たとえば、このプラグインは一連の.rotate-{angle}ユーティリティを生成します。ここで、{angle}はユーザーが指定した文字列です。 e関数は、連結されたクラス名をエスケープして、.rotate-1/4のようなクラスが期待どおりに機能することを確認するために使用されます。

// tailwind.config.js
const _ = require('lodash')
const plugin = require('tailwindcss/plugin')

module.exports = {
  theme: {
    rotate: {
      '1/4': '90deg',
      '1/2': '180deg',
      '3/4': '270deg',
    }
  },
  plugins: [
    plugin(function({ addUtilities, config, e }) {
      const rotateUtilities = _.map(config('theme.rotate'), (value, key) => {
        return {
          [`.${e(`rotate-${key}`)}`]: {
            transform: `rotate(${value})`
          }
        }
      })

      addUtilities(rotateUtilities)
    })
  ]
}

このプラグインは次のCSSを生成します。

.rotate-1\/4 {
  transform: rotate(90deg);
}
.rotate-1\/2 {
  transform: rotate(180deg);
}
.rotate-3\/4 {
  transform: rotate(270deg);
}

実際にエスケープしたいコンテンツのみをエスケープするように注意してください。 クラス名の先頭の.や、:hover:focusなどの疑似クラスの先頭にある:を渡さないでください。そうしないと、これらの文字はエスケープされます。

さらに、CSSには、クラス名で開始できる文字に関するルールがあるため(クラスは数字で始めることはできませんが、数字を含めることができます)、 完全なクラス名(ユーザー提供部分だけではなく)をエスケープすることをお勧めします。そうでなければ、不要なエスケープシーケンスが発生する可能性があります。

// Will unnecessarily escape `1`
`.rotate-${e('1/4')}`
// => '.rotate-\31 \/4'

// Won't escape `1` because it's not the first character
`.${e('rotate-1/4')}`
// => '.rotate-1\/4'

Manually prefixing selectors

特定のクラスにのみプレフィックスを付ける複雑なものを作成している場合は、prefix関数を使用して、ユーザーが構成したプレフィックスをいつ適用するかをきめ細かく制御できます。

たとえば、セレクターに既存のクラスを含む内部プロジェクトのセット全体で再利用するプラグインを作成している場合は、新しいクラスのプレフィックスのみを付けることができます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  prefix: 'tw-',
  plugins: [
    plugin(function({ addComponents, prefix }) {
      addComponents({
        [`.existing-class > ${prefix('.new-class')}`]: {
          backgroundColor: '#fff',
        }
      })
    })
  ]
}

これにより、次のCSSが生成されます。

.existing-class > .tw-new-class {
  background-color: #fff;
}

prefix関数はセレクター内のすべてのクラスにプレフィックスを付け、非クラスを無視するため、次のような複雑なセレクターを渡すのは完全に安全です。

prefix('.btn-blue .w-1\/4 > h1.text-xl + a .bar')
// => '.tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar'

Referencing the user's config

config, theme、および variants関数を使用すると、ドット表記を使用してユーザーのTailwind構成から値を要求でき、そのパスが存在しない場合はデフォルト値を提供します。

たとえば、組み込みのcontainerプラグインのこの簡略化されたバージョンは、theme関数を使用してユーザーの構成済みブレークポイントを取得します。

// tailwind.config.js
const _ = require('lodash')
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents, theme }) {
      const screens = theme('screens', {})

      const mediaQueries = _.map(screens, width => {
        return {
          [`@media (min-width: ${width})`]: {
            '.container': {
              'max-width': width,
            },
          },
        }
      })

      addComponents([
        { '.container': { width: '100%' } },
        ...mediaQueries,
      ])
    })
  ]
}

theme関数は、実際にはconfig関数を使用してユーザーの構成のテーマセクションにアクセスするための単なるショートカットであることに注意してください。

// These are equivalent
config('theme.screens')
theme('screens')

ユーザーのvariants構成を参照する場合は、config関数の代わりにvariants()関数を使用することをお勧めします。

バリアントを検索するためにconfig関数を使用しないでください

addUtilities(newUtilities, config('variants.customPlugin'))

代わりにバリアント関数を使用してください

addUtilities(newUtilities, variants('customPlugin'))

variantsは、プロジェクト全体のすべてのプラグインに対して構成するバリアントのグローバルリストである可能性があるため、Variants()関数を使用すると、ロジックを自分で再実装しなくても、ユーザーの構成を簡単に尊重できます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  variants: ['responsive', 'hover', 'focus'],
  plugins: [
    plugin(function ({ config, variants }) {
      config('variants.customPlugin')
      // => undefined

      variants('customPlugin')
      // => ['reponsive', 'hover', 'focus']
    })
  ]
}

Exposing options

プラグインがプラグインの動作をカスタマイズするためにユーザーが構成できる独自のオプションを公開することは、多くの場合意味があります。

これを実現する最善の方法は、ユーザーのthemeおよびvariants構成で独自のキーを要求し、themeおよびvariants関数でアクセスできるように、そこにオプションを提供するように依頼することです。

たとえば、オプションとして生成するグラデーションとバリアントを受け入れる単純なグラデーションユーティリティを作成するためのプラグイン(独自のモジュールに抽出)を次に示します。

// ./plugins/gradients.js
const _ = require('lodash')
const plugin = require('tailwindcss/plugin')

module.exports = plugin(function({ addUtilities, e, theme, variants }) {
  const gradients = theme('gradients', {})
  const gradientVariants = variants('gradients', [])

  const utilities = _.map(gradients, ([start, end], name) => ({
    [`.${e(`bg-gradient-${name}`)}`]: {
      backgroundImage: `linear-gradient(to right, ${start}, ${end})`
    }
  }))

  addUtilities(utilities, gradientVariants)
})

これを使用するには、プラグインリストで必須にし、themevariantsの両方のgradientsキーで構成を指定します。

// tailwind.config.js
module.exports = {
  theme: {
    gradients: theme => ({
      'blue-green': [theme('colors.blue.500'), theme('colors.green.500')],
      'purple-blue': [theme('colors.purple.500'), theme('colors.blue.500')],
      // ...
    })
  },
  variants: {
    gradients: ['responsive', 'hover'],
  },
  plugins: [
    require('./plugins/gradients')
  ],
}

Providing default options

プラグインにデフォルトのthemeおよびvariantsオプションを提供するには、デフォルトを含むTailwindのplugin関数に2番目の引数を渡します。

// ./plugins/gradients.js
const _ = require('lodash')
const plugin = require('tailwindcss/plugin')

module.exports = plugin(function({ addUtilities, e, theme, variants }) {
  // ...
}, {
  theme: {
    gradients: theme => ({
      'blue-green': [theme('colors.blue.500'), theme('colors.green.500')],
      'purple-blue': [theme('colors.purple.500'), theme('colors.blue.500')],
      // ...
    })
  },
  variants: {
    gradients: ['responsive', 'hover'],
  }
})

このオブジェクトは単なる別のTailwind構成オブジェクトであり、tailwind.config.jsでの操作に慣れている構成オブジェクトと同じプロパティと機能をすべて備えています。

この方法でデフォルトを提供することにより、エンドユーザーはデフォルトを上書き および拡張できるようになります。Tailwindの組み込みスタイルでできるのと同じ方法です。


CSS-in-JS syntax

addUtilities,addComponentsおよびaddBaseはそれぞれ、JavaScriptオブジェクトとして記述されたCSSルールを想定しています。 Tailwindは、EmotionなどのCSS-in-JSライブラリから認識できるのと同じ種類の構文を使用し、内部でpostcss-jsを利用しています。

Consider this simple CSS rule:

.card {
  background-color: #fff;
  border-radius: .25rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

これをCSS-in-JSオブジェクトに変換すると、次のようになります。

addComponents({
  '.card': {
    'background-color': '#fff',
    'border-radius': '.25rem',
    'box-shadow': '0 2px 4px rgba(0,0,0,0.2)',
  }
})

便宜上、プロパティ名はキャメルケースで記述することもでき、自動的にダッシュケースに変換されます。

addComponents({
  '.card': {
    backgroundColor: '#fff',
    borderRadius: '.25rem',
    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
  }
})

SassやLessでおなじみの構文を使用して、ネストもサポートされています([postcss-nested]https://github.com/postcss/postcss-nested)を利用)。

addComponents({
  '.card': {
    backgroundColor: '#fff',
    borderRadius: '.25rem',
    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
    '&:hover': {
      boxShadow: '0 10px 15px rgba(0,0,0,0.2)',
    }
    '@media (min-width: 500px)': {
      borderRadius: '.5rem',
    }
  }
})

同じオブジェクトで複数のルールを定義できます。

addComponents({
  '.btn': {
    padding: '.5rem 1rem',
    borderRadius: '.25rem',
    fontWeight: '600',
  },
  '.btn-blue': {
    backgroundColor: '#3490dc',
    color: '#fff',
    '&:hover': {
      backgroundColor: '#2779bd'
    },
  },
  '.btn-red': {
    backgroundColor: '#e3342f',
    color: '#fff',
    '&:hover': {
      backgroundColor: '#cc1f1a'
    },
  },
})

...または同じキーを繰り返す必要がある場合のオブジェクトの配列として:

addComponents([
  {
    '@media (min-width: 500px)': {
      // ...
    }
  },
  {
    '@media (min-width: 500px)': {
      // ...
    }
  },
  {
    '@media (min-width: 500px)': {
      // ...
    }
  },
])

Adding variants

addVariant関数を使用すると、組み込みのホバー、フォーカス、アクティブなどのバリアントと同じように使用できる独自のカスタムvariantsを登録できます。

新しいバリアントを追加するには、addVariant関数を呼び出して、カスタムバリアントの名前と、影響を受けるCSSルールを必要に応じて変更するコールバックを渡します。

const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addVariant, e }) {
      addVariant('disabled', ({ modifySelectors, separator }) => {
        modifySelectors(({ className }) => {
          return `.${e(`disabled${separator}${className}`)}:disabled`
        })
      })
    })
  ]
}

コールバックは、次の部分に分解できるオブジェクトを受け取ります。

  • modifySelectors 基本的なバリアントの追加を簡素化するヘルパー関数
  • separator ユーザーが構成した区切り文字列
  • container 複雑なバリアントを作成するために、バリアントが適用されているすべてのルールを含むPostCSSコンテナ

Basic variants

セレクターを変更するだけでよい単純なバリアントを追加する場合は、modifySelectorsヘルパーを使用します。

modifySelectorsヘルパーは、次の部分に分解できるオブジェクトを受け取る関数を受け入れます。

  • selector 現在のルールの完全な変更されていないセレクター
  • className 現在のルールのクラス名先頭のドットを削除

modifySelectorsに渡す関数は、変更されたセレクターを返すだけです。

たとえば、first-childバリアントプラグインは次のように記述できます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addVariant, e }) {
      addVariant('first-child', ({ modifySelectors, separator }) => {
        modifySelectors(({ className }) => {
          return `.${e(`first-child${separator}${className}`)}:first-child`
        })
      })
    })
  ]
}

Complex variants

セレクターを変更するだけでなく(実際のルール宣言を変更したり、ルールを別のat-ruleでラップしたりするなど)何かを行う必要がある場合は、containerインスタンスを使用する必要があります。

containerインスタンスを使用すると、特定のモジュールまたは@variantsブロック内のすべてのルールを横断して、標準のPostCSS APIを使用して好きなように操作できます。

たとえば、このプラグインは、クラスの前に感嘆符を付け、各宣言をimportantに変更することにより、影響を受ける各ユーティリティのimportantバージョンを作成します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addVariant }) {
      addVariant('important', ({ container }) => {
        container.walkRules(rule => {
          rule.selector = `.\\!${rule.selector.slice(1)}`
          rule.walkDecls(decl => {
            decl.important = true
          })
        })
      })
    })
  ]
}

このプラグインは、コンテナ内のすべてのルールを取得し、それらを@supports(display:grid) at-ruleでラップし、各ルールの前にsupports-gridを付けます。

// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addVariant, e, postcss }) {
      addVariant('supports-grid', ({ container, separator }) => {
        const supportsRule = postcss.atRule({ name: 'supports', params: '(display: grid)' })
        supportsRule.append(container.nodes)
        container.append(supportsRule)
        supportsRule.walkRules(rule => {
          rule.selector = `.${e(`supports-grid${separator}${rule.selector.slice(1)}`)}`
        })
      })
    })
  ]
}

PostCSSを直接操作する方法の詳細については、PostCSS APIドキュメントを確認してください。

Using custom variants

カスタムバリアントの使用は、Tailwindの組み込みバリアントの使用と同じです。

Tailwindのコアプラグインでカスタムバリアントを使用するには、それらを構成ファイルのvariantsセクションに追加します。

// tailwind.config.js
modules.exports = {
  variants: {
    borderWidths: ['responsive', 'hover', 'focus', 'first-child', 'disabled'],
  }
}

独自のCSSでカスタムユーティリティを使用してカスタムバリアントを使用するには、variants at-ruleを使用します。

@variants hover, first-child {
  .bg-cover-image {
    background-image: url('/path/to/image.jpg');
  }
}

Translated by T.Arai @ Entap,Inc. / スマホアプリ開発会社

Tailwind UI is now in early access!