本文へ移動

css ヘルパー ​

CSS ヘルパー hono/css は、Hono 組み込みの CSS in JS(X) 機能です。

JSX 内で、css という JavaScript テンプレートリテラルに CSS を記述できます。css の戻り値はクラス名で、class 属性の値として設定します。CSS の内容は <Style /> コンポーネントに含まれます。

インポート ​

ts
import { Hono } from 'hono'
import { css, cx, keyframes, Style, createCssContext } from 'hono/css'

css Experimental ​

css テンプレートリテラルに CSS を記述できます。この例では、headerClass を class 属性の値に使います。CSS の内容を含む <Style /> を忘れずに追加してください。

ts
app.get('/', (c) => {
  const headerClass = css`
    background-color: orange;
    color: white;
    padding: 1rem;
  `
  return c.html(
    <html>
      <head>
        <Style />
      </head>
      <body>
        <h1 class={headerClass}>Hello!</h1>
      </body>
    </html>
  )
})

ネストセレクターの & を使って、:hover などの擬似クラスのスタイルを設定できます。

ts
const buttonClass = css`
  background-color: #fff;
  &:hover {
    background-color: red;
  }
`

拡張 ​

クラス名を埋め込むことで、CSS 定義を拡張できます。

tsx
const baseClass = css`
  color: white;
  background-color: blue;
`

const header1Class = css`
  ${baseClass}
  font-size: 3rem;
`

const header2Class = css`
  ${baseClass}
  font-size: 2rem;
`

さらに、${baseClass} {} 構文を使うと、クラスをネストできます。

tsx
const headerClass = css`
  color: white;
  background-color: blue;
`
const containerClass = css`
  ${headerClass} {
    h1 {
      font-size: 3rem;
    }
  }
`
return c.render(
  <div class={containerClass}>
    <header class={headerClass}>
      <h1>Hello!</h1>
    </header>
  </div>
)

グローバルスタイル ​

:-hono-global という擬似セレクターで、グローバルスタイルを定義できます。

tsx
const globalClass = css`
  :-hono-global {
    html {
      font-family: Arial, Helvetica, sans-serif;
    }
  }
`

return c.render(
  <div class={globalClass}>
    <h1>Hello!</h1>
    <p>Today is a good day.</p>
  </div>
)

css リテラルを使い、<Style /> コンポーネント内に CSS を記述することもできます。

tsx
export const renderer = jsxRenderer(({ children, title }) => {
  return (
    <html>
      <head>
        <Style>{css`
          html {
            font-family: Arial, Helvetica, sans-serif;
          }
        `}</Style>
        <title>{title}</title>
      </head>
      <body>
        <div>{children}</div>
      </body>
    </html>
  )
})

keyframes Experimental ​

keyframes を使って @keyframes の内容を記述できます。この例では、fadeInAnimation がアニメーション名になります。

tsx
const fadeInAnimation = keyframes`
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
`
const headerClass = css`
  animation-name: ${fadeInAnimation};
  animation-duration: 2s;
`
const Header = () => <a class={headerClass}>Hello!</a>

cx Experimental ​

cx は 2 つのクラス名を合成します。

tsx
const buttonClass = css`
  border-radius: 10px;
`
const primaryClass = css`
  background: orange;
`
const Button = () => (
  <a class={cx(buttonClass, primaryClass)}>Click!</a>
)

単純な文字列を合成することもできます。

tsx
const Header = () => <a class={cx('h1', primaryClass)}>Hi</a>

Secure Headers ミドルウェアとの併用 ​

CSS ヘルパーを Secure Headers ミドルウェアと併用する場合、<Style nonce={c.get('secureHeadersNonce')} /> に nonce 属性を追加することで、CSS ヘルパーによる Content-Security-Policy の問題を回避できます。

tsx
import { secureHeaders, NONCE } from 'hono/secure-headers'

app.get(
  '*',
  secureHeaders({
    contentSecurityPolicy: {
      // Set the pre-defined nonce value to `styleSrc`:
      styleSrc: [NONCE],
    },
  })
)

app.get('/', (c) => {
  const headerClass = css`
    background-color: orange;
    color: white;
    padding: 1rem;
  `
  return c.html(
    <html>
      <head>
        {/* Set the `nonce` attribute on the CSS helpers `style` and `script` elements */}
        <Style nonce={c.get('secureHeadersNonce')} />
      </head>
      <body>
        <h1 class={headerClass}>Hello!</h1>
      </body>
    </html>
  )
})

createCssContext Experimental ​

createCssContext は、カスタムコンテキストを持つ CSS ヘルパー関数(css、cx、keyframes、viewTransition、Style)を作成します。スタイル要素の ID や、生成されるクラス名をカスタマイズできます。

ts
import { createCssContext } from 'hono/css'

const { css, cx, keyframes, Style } = createCssContext({
  id: 'my-app',
})

classNameSlug ​

デフォルトでは、CSS クラス名は css-1234567890 形式で生成されます。classNameSlug 関数を渡すことでカスタマイズできます。

この関数は 3 つの引数を受け取ります。

  • hash - デフォルトで生成されるクラス名(例:css-1234567890)
  • label - CSS テンプレート先頭の /* comment */ から抽出したラベル(なければ空文字列)
  • css - 圧縮された CSS 文字列
ts
const { css, Style } = createCssContext({
  id: 'my-styles',
  classNameSlug: (hash, label) => (label ? `h-${label}` : hash),
})

const heroClass = css`
  /* hero-section */
  background: blue;
`
// Generated class name: "h-hero-section"

onInvalidSlug ​

classNameSlug 関数が無効な CSS クラス名を返した場合、デフォルトでは警告を記録します。onInvalidSlug でこの動作をカスタマイズできます。

ts
const { css, Style } = createCssContext({
  id: 'my-styles',
  classNameSlug: (hash, label) => label || hash,
  onInvalidSlug: (slug) => {
    throw new Error(`Invalid CSS class name: ${slug}`)
  },
})

セキュリティ ​

CSS ヘルパーは CSS を記述する API です。他の CSS-in-JS ライブラリと同様に、補間された値は生の CSS として挿入されます。HTML への脱出につながる引用符、バックスラッシュ、</ は防ぎますが、{、}、; は有効な CSS なのでそのまま通します。

注意

CSS ヘルパーは、他の生の出力先(html、raw、rawCssString)と同様に扱ってください。信頼できない入力を直接渡さないでください。 CSS インジェクションが可能になります。まず許可リストで検証してください。

tsx
const ALLOWED_COLORS = ['red', 'green', 'blue']
const color = ALLOWED_COLORS.includes(input) ? input : 'black'
const headerClass = css`
  color: ${color};
`

ヒント ​

VS Code では、vscode-styled-components を使うと、CSS のタグ付きテンプレートリテラルで構文の強調表示と IntelliSense を利用できます。

MIT ライセンスで公開されています。