diff --git a/apps/site/src/content/en/changelog.mdx b/apps/site/src/content/en/changelog.mdx index 9cefbdb88..e5a63d778 100644 --- a/apps/site/src/content/en/changelog.mdx +++ b/apps/site/src/content/en/changelog.mdx @@ -15,6 +15,7 @@ Significant changes may continue until v1.0 is released, so all notable changes - For components added with `lism-cli ui add` before v0.26.0, change `@layer lism-component` to `lism-block` in their `_style.css`. - **Breaking:** Removed the deprecated component aliases `SideMain` / `FluidCols` / `SwitchCols` and the `Dummy` component from `lism-css/react` and `lism-css/astro`. Migrate to `WithSide` / `AutoColumns` / `SwitchColumns` and `DummyText` from `@lism-css/ui`, respectively (#646). - **Breaking:** Removed `_propConfig`, which overrode the Lism Props conversion settings per prop, from `getLismProps` and component props. To change the conversion settings, use [`props` in `lism.config.js`](/en/docs/customize/config/) instead (#225). +- `@lism-css/ui`: Fixed `lism-base` styles taking priority over `b--*` styles when component CSS is output before `lism-css/main.css` (for example, a `Button` rendered as an `a` element kept its underline and link color). Each component's CSS and `style.css` now declare the order of `lism-base` and `lism-block` at the top (#644). diff --git a/apps/site/src/content/ja/changelog.mdx b/apps/site/src/content/ja/changelog.mdx index 440d4b1ed..983298186 100644 --- a/apps/site/src/content/ja/changelog.mdx +++ b/apps/site/src/content/ja/changelog.mdx @@ -15,6 +15,7 @@ v1.0のリリースまでは大きな変更が続く可能性があるため、 - v0.26.0より前に`lism-cli ui add`で追加したコンポーネントは、`_style.css`の`@layer lism-component`を`lism-block`へ書き換えてください。 - **破壊的変更:** 非推奨だったコンポーネントエイリアス`SideMain` / `FluidCols` / `SwitchCols`と`Dummy`コンポーネントを、`lism-css/react`と`lism-css/astro`から削除しました。それぞれ`WithSide` / `AutoColumns` / `SwitchColumns`と`@lism-css/ui`の`DummyText`へ移行してください(#646)。 - **破壊的変更:** Lism Propsの変換設定をprop単位で上書きする`_propConfig`を、`getLismProps`と各コンポーネントのpropsから削除しました。変換設定を変えたい場合は、[`lism.config.js`の`props`](/docs/customize/config/)で設定してください(#225)。 +- `@lism-css/ui`: コンポーネントのCSSが`lism-css/main.css`より前に出力されると、`lism-base`のスタイルが`b--*`のスタイルより優先される不具合を修正しました(`a`要素の`Button`に下線とリンク色が残るなど)。各コンポーネントのCSSと`style.css`の先頭で、`lism-base`と`lism-block`の順序を宣言します(#644)。 diff --git a/docs/decisions.md b/docs/decisions.md index e4e19cc10..9037c374d 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -1,7 +1,17 @@ -基準日: 2026-09-28・コミットc9321bcec +基準日: 2026-09-29・コミット4aa2aa5c6 # 意思決定の記録 +## 2026-09-29: `@lism-css/ui`のコンポーネントCSSは先頭で`lism-base, lism-block`の順序だけを宣言する + +各コンポーネントは自分の`_style.css`をimportしており、そのCSSの出力位置は利用側のバンドラが決める。`lism-css/main.css`より前に出力されると`lism-block`が最初のレイヤーとして登録され、`lism-base`より弱くなる(#644)。`a`要素の`Button`に下線とリンク色が残った。 + +- 決定: `packages/lism-ui/src/components/*/_style.css`の各先頭に`@layer lism-base, lism-block;`を置き、全ファイルにあることをテストで検査する。`@layer`文は登録済みのレイヤーの位置を変えず、未登録のレイヤーを宣言順に後ろへ足す。先頭の2層を固定すれば、後から読まれる`main.css`の宣言で残りの層が正しい順に並ぶ。ui側のCSSが使うレイヤーは`lism-block`だけ。 +- 却下: 6層すべてを宣言する案。レイヤーを増減するたびにui側も直すことになる(`lism-component`の削除がその例)。 +- 却下: コンポーネント側のCSSの自動読み込みをやめ、`style.css`に一本化する案。利用者に`style.css`の読み込みを求める破壊的変更になり、使うコンポーネントのCSSだけを読み込む利点も失う。 +- 却下: ビルド時に`dist`のCSSへ宣言を付与する案。Astro版は`src`の`_style.css`を読むため効かない。 +- 決定: `dist/style.css`は全コンポーネントの`_style.css`を結合するため、同じ宣言がコンポーネントの数だけ入る。`packages/lism-ui/build-css.js`がコンパイル後に先頭の1つだけ残し、1つも無ければビルドを失敗させる。共有の`compileCssTree`と`dist/components/*/_style.css`は変えない。 + ## 2026-09-28: 後方互換用の`lism-component`レイヤーを削除する v0.26.0で`c--`を`b--`へ移行したとき(#546, #559)、旧`c--`の配置先だった`lism-component`は宣言だけ残した。宣言から消すと、利用者CSSの`@layer lism-component`が初出の位置で登録され、優先度が変わるため。空のレイヤーが公開文書の6層の表記と食い違うという報告があり、v1へ持ち越さずに互換を切る。 diff --git a/packages/lism-ui/build-css.js b/packages/lism-ui/build-css.js index 5f30033c7..d96fa7b46 100644 --- a/packages/lism-ui/build-css.js +++ b/packages/lism-ui/build-css.js @@ -1,3 +1,4 @@ +import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // P1 で bin/build-css.js(compileSCSS)は共有コアへ統合された。 @@ -7,6 +8,21 @@ import { compileCssTree } from '@lism-css/plugin/builder'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +const LAYER_ORDER_PATTERN = /@layer\s+lism-base\s*,\s*lism-block\s*;/g; + +// style.css は全コンポーネントの _style.css を結合するため、同じ順序宣言が重複する。先頭の1つだけ残す。 +function dedupeLayerOrder(cssPath) { + const css = fs.readFileSync(cssPath, 'utf8'); + + let count = 0; + const deduped = css.replace(LAYER_ORDER_PATTERN, (match) => (count++ === 0 ? match : '')); + if (count === 0) { + throw new Error(`[build-css] ${path.relative(__dirname, cssPath)} にレイヤー順序宣言が見つかりません`); + } + + fs.writeFileSync(cssPath, deduped); +} + // デフォルトエクスポート(他から await 可能) async function buildCSS() { // パス(絶対パスに変換) @@ -14,6 +30,7 @@ async function buildCSS() { const distDir = path.resolve(__dirname, './dist/'); await compileCssTree({ scssDir, distDir }); + dedupeLayerOrder(path.resolve(distDir, 'style.css')); } buildCSS(); diff --git a/packages/lism-ui/src/components/Accordion/_style.css b/packages/lism-ui/src/components/Accordion/_style.css index db7fbc260..9d2621eb2 100644 --- a/packages/lism-ui/src/components/Accordion/_style.css +++ b/packages/lism-ui/src/components/Accordion/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { .b--accordion { --duration: var(--acc-duration, 0.25s); diff --git a/packages/lism-ui/src/components/Alert/_style.css b/packages/lism-ui/src/components/Alert/_style.css index 2959ae1f7..4ccba7845 100644 --- a/packages/lism-ui/src/components/Alert/_style.css +++ b/packages/lism-ui/src/components/Alert/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { /* Note: u--cbox との併用を前提としています。layout="withSide" 指定時は l--withSide がレイアウトを上書きします. */ .b--alert { diff --git a/packages/lism-ui/src/components/Avatar/_style.css b/packages/lism-ui/src/components/Avatar/_style.css index f77535b9a..ad0164010 100644 --- a/packages/lism-ui/src/components/Avatar/_style.css +++ b/packages/lism-ui/src/components/Avatar/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { /* src ありの通常時: l--frame との併用が前提 */ .b--avatar { diff --git a/packages/lism-ui/src/components/Badge/_style.css b/packages/lism-ui/src/components/Badge/_style.css index 610468504..dbc594774 100644 --- a/packages/lism-ui/src/components/Badge/_style.css +++ b/packages/lism-ui/src/components/Badge/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { .b--badge { --c: var(--base); diff --git a/packages/lism-ui/src/components/Button/_style.css b/packages/lism-ui/src/components/Button/_style.css index bc3ace2d5..80801bcb8 100644 --- a/packages/lism-ui/src/components/Button/_style.css +++ b/packages/lism-ui/src/components/Button/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { .b--button { --c: var(--base); diff --git a/packages/lism-ui/src/components/Callout/_style.css b/packages/lism-ui/src/components/Callout/_style.css index 2af086da1..3943ad57b 100644 --- a/packages/lism-ui/src/components/Callout/_style.css +++ b/packages/lism-ui/src/components/Callout/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { /* Note: l--stack と u--cbox との併用を前提としています. */ .b--callout { diff --git a/packages/lism-ui/src/components/Details/_style.css b/packages/lism-ui/src/components/Details/_style.css index f1658deb1..009d93a08 100644 --- a/packages/lism-ui/src/components/Details/_style.css +++ b/packages/lism-ui/src/components/Details/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; /* ::details-content と grid-template-rows: 0fr → 1fr のトランジションでアニメーション */ @layer lism-block { .b--details { diff --git a/packages/lism-ui/src/components/Modal/_style.css b/packages/lism-ui/src/components/Modal/_style.css index 0edbfea98..41f0e19a1 100644 --- a/packages/lism-ui/src/components/Modal/_style.css +++ b/packages/lism-ui/src/components/Modal/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; /* * dialog で実装. * Memo: ::backdrop のアニメーションはFirefoxで動かない diff --git a/packages/lism-ui/src/components/NavMenu/_style.css b/packages/lism-ui/src/components/NavMenu/_style.css index 7b427ed84..1f6b73741 100644 --- a/packages/lism-ui/src/components/NavMenu/_style.css +++ b/packages/lism-ui/src/components/NavMenu/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { .b--navMenu { --link-td: none; diff --git a/packages/lism-ui/src/components/Popover/_style.css b/packages/lism-ui/src/components/Popover/_style.css index 808de46f3..66bfb5775 100644 --- a/packages/lism-ui/src/components/Popover/_style.css +++ b/packages/lism-ui/src/components/Popover/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; /* * ネイティブ Popover API(popover 属性)で開閉し、CSS Anchor Positioning で配置する。 * anchor 配置は @supports で囲い、非対応ブラウザ(部分対応の Chrome 128〜130 を含む)には diff --git a/packages/lism-ui/src/components/ShapeDivider/_style.css b/packages/lism-ui/src/components/ShapeDivider/_style.css index eb0f38d0a..12031b136 100644 --- a/packages/lism-ui/src/components/ShapeDivider/_style.css +++ b/packages/lism-ui/src/components/ShapeDivider/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; /* Note: scale 反転は、__inner に対して行う。shapeDivider自身を反転させると、X方向に隙間ができてしまう。 */ @layer lism-block { .b--shapeDivider { diff --git a/packages/lism-ui/src/components/Tabs/_style.css b/packages/lism-ui/src/components/Tabs/_style.css index 751ead523..f69a66c2d 100644 --- a/packages/lism-ui/src/components/Tabs/_style.css +++ b/packages/lism-ui/src/components/Tabs/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; @layer lism-block { /* l--grid との併用が前提 */ .b--tabs { diff --git a/packages/lism-ui/src/components/Tooltip/_style.css b/packages/lism-ui/src/components/Tooltip/_style.css index 9280412b8..b499d88c9 100644 --- a/packages/lism-ui/src/components/Tooltip/_style.css +++ b/packages/lism-ui/src/components/Tooltip/_style.css @@ -1,3 +1,4 @@ +@layer lism-base, lism-block; /* * 表示は CSS(:hover / :focus-visible)、配置は CSS Anchor Positioning。 * anchor 配置は @supports で囲い、非対応(部分対応の Chrome 128〜130 含む)はルート基準の absolute 配置に落とす。 diff --git a/packages/lism-ui/src/components/styleLayerOrder.test.ts b/packages/lism-ui/src/components/styleLayerOrder.test.ts new file mode 100644 index 000000000..258aa1a64 --- /dev/null +++ b/packages/lism-ui/src/components/styleLayerOrder.test.ts @@ -0,0 +1,25 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const componentsDir = dirname(fileURLToPath(import.meta.url)); + +// バンドラがコンポーネントCSSを lism-css/main.css より前に出力しても、lism-block が lism-base より後ろになるよう先頭で順序を固定する +const LAYER_ORDER = /^@layer lism-base, lism-block;\r?\n/; + +const styleFiles = readdirSync(componentsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => `${entry.name}/_style.css`) + .filter((file) => existsSync(resolve(componentsDir, file))); + +describe('コンポーネントCSSのレイヤー順序宣言', () => { + it('検査対象の _style.css が見つかる', () => { + expect(styleFiles.length).toBeGreaterThan(0); + }); + + it.each(styleFiles)('%s の先頭に順序宣言がある', (file) => { + const css = readFileSync(resolve(componentsDir, file), 'utf8'); + expect(css).toMatch(LAYER_ORDER); + }); +});