Customize
Lism CSS のnpmパッケージ lism-css から読み込めるCSSやコンポーネントで受け付けるプロパティに関する設定を上書きしてカスタマイズする方法を紹介します。
@layerをオフにする
lism-css/main.css ではなく、lism-css/main_no_layer.css を読み込むことで、@layerをオフにすることができます。
@layerをオフにするだけであれば、読み込むCSSファイルを切り替えるだけですみます。
SCSS でのカスタマイズ
SCSSの変数を上書きすることで、読み込むCSSのカスタマイズが可能です。
上書き可能な変数
lism-css/scss/_setting.scssで定義している変数を @use ... with (...) で上書きできます。
_setting.scss@use 'sass:map';
@use 'sass:string';
@use './prop-config' as props;
$default_important: 0 !default;
$is_container_query: 1 !default;
$common_support_bp: 'md' !default;
$breakpoints: () !default;
$breakpoints: map.merge(
(
'sm': '480px',
'md': '800px',
'lg': '1120px',
),
$breakpoints
);
$props: () !default;
$props: map.deep-merge(props.$props, $props); | 変数 | 用途 |
|---|---|
$breakpoints | ブレイクポイント数値の定義 |
$common_support_bp | 主要な Property Class が共通してサポートするブレイクポイント |
$is_container_query | コンテナクエリで出力するか(1 = container query / 0 = media query) |
$default_important | Property Class にデフォルトで !important を付与するか(0 / 1) |
$props | Property Class ごとの個別出力設定 |
基本フォーマット
上書き定義をしてから lism-css/scss/main.scss を読み込むことで、スタイルをカスタマイズできます。
// 1. 設定変数を上書き@use '../path-to/node_modules/lism-css/scss/setting' with ( $breakpoints: ( 'sm': '400px', // 個別キーの上書き可 ), $common_support_bp: 'lg', // 'sm' | 'md' | 'lg' $is_container_query: 0, // 1 = container query / 0 = media query $default_important: 1, // 0 / 1 $props: ( // 個別 Prop の設定(後述) ));
// 2. main.scss を読み込む(@layer を使わない場合は main_no_layer.scss)@use '../path-to/node_modules/lism-css/scss/main';@layerのオン・オフは変数管理ではありません。読み込むmain.scssを切り替えてください。- Astro の場合は
../path-to/node_modules/部分は不要です。
$props の個別カスタマイズ
各 Property Class について、出力範囲やユーティリティクラスを追加できます。
@use '../path-to/node_modules/lism-css/scss/setting' with ( $props: ( 'fz': ( important: 1, // fzのユーティリティクラスに対して !important を出力する ), 'h': ( bp: 0, // 'h'のブレイクポイント対応クラスを出力しないようにする ), 'p': ( bp: 'lg', // 'p'のユーティリティクラスは、 lg サイズまで出力する utilities: ( 'box': '2em', ), //.-p:box{--p:2em} を追加する ), ));
@use '../path-to/node_modules/lism-css/scss/main';SCSSのコンパイル処理について
SCSSを直接読み込んで使用する場合、コンパイル時にlism-cssのCSSと読み込み順がずれたりする可能性があることに注意してください。
lism.config.js でのカスタマイズ
プロジェクトのルートディレクトリ直下に lism.config.js(または lism.config.mjs)を用意することで、スタイルではなくコンポーネントの挙動をカスタマイズすることができます。
Vite プラグインの登録(必須)
lism.config.js を読み込ませるには、Vite(または Astro)の設定ファイルで lism-css/vite-plugin を登録する必要があります。登録していない場合、ファイルを置いてもデフォルト設定のままになります。
import { defineConfig } from 'astro/config';import lismCss from 'lism-css/vite-plugin';
export default defineConfig({ vite: { plugins: [lismCss()], },});プラグインはプロジェクトルートから lism.config.js → lism.config.mjs の順で自動検出します。別の場所のファイルを使う場合は configPath オプションでパスを指定できます。
plugins: [lismCss({ configPath: './config/lism.config.js' })],フォーマット
export default { props: { hoge: { ... }, foo: { ... }, ... }, tokens: { hoge: [...], foo: [...], ... }, traits: { isHoge: 'is--hoge', setFoo: 'set--foo', ... },};/**
* isVar: 1 → クラス出力はせずstyle属性での変数出力のみ (--bdw, --keycolor など)
* bp: 0 → Prop-valユーティリティクラス化されなければ、style属性で出力するだけ。
* bp: 1 → .-prop と --prop の セットがベースにあり、.-prop_bp と .--prop_bp で ブレイクポイント指定できる。
* .-prop{property:var(--prop)} が基本で、ユーティリティクラスは .-prop:val{property:value} となる。
*
* ↓コンポーネント処理で使用される
* tokenClass: 1 → 対応するトークン値がそのまま全てユーティリティクラス化されるもの。
* 2 → ユーティリティクラスされ、内容は別途ファイルで記述するもの。
* shorthands: → コンポーネント側で短く書くための設定
*
* ↓SCSS出力で使用される
* alwaysVar: 1 → state変数扱い。 .-prop,[class*=-prop:] {property:var(--prop)} の base 出力となり、
* ユーティリティクラスは --prop をセットする形になる。
* 加えて BPクラスも .-prop_$bp { property: var(--prop); --prop: var(--prop_$bp) !important; } を出力し、
* 常に --prop が当該要素の現在値になるよう上書きされる(consumer が --prop を参照できる)。
* important: 1 → !important を付けて最終的に出力する
*/
const PLACE_PRESETS = ['start', 'center', 'end'] as const;
const PLACE_FX_PRESETS = ['flex-start', 'flex-end'] as const;
const PLACE_SHORTHANDS = { s: 'start', e: 'end', c: 'center', fs: 'flex-start', fe: 'flex-end' } as const;
export default {
f: { prop: 'font', presets: ['inherit'] },
fz: { prop: 'fontSize', token: 'fz', tokenClass: 1, bp: 1 },
fw: {
prop: 'fontWeight',
token: 'fw',
tokenClass: 1,
presets: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
},
ff: { prop: 'fontFamily', token: 'ff', tokenClass: 1 },
fs: { prop: 'fontStyle', presets: ['italic'], shorthands: { i: 'italic' } },
lh: {
prop: 'lineHeight',
presets: ['1'],
token: 'hl',
tokenClass: 2,
exUtility: { 1: '' },
},
hl: {
isVar: 1,
token: 'hl',
tokenClass: 0,
bp: 1,
},
lts: { prop: 'letterSpacing', token: 'lts', tokenClass: 1 },
ta: { prop: 'textAlign', presets: ['center', 'left', 'right'] },
td: { prop: 'textDecoration', utils: { none: 'none' } },
tt: { prop: 'textTransform', utils: { upper: 'uppercase', lower: 'lowercase' } },
// te: { prop: 'textEmphasis', presets: ['filled'] },
// tsh: { prop: 'textShadow' },
d: {
prop: 'display',
presets: ['none', 'block', 'flex', 'inline-flex', 'grid', 'inline-grid', 'inline', 'inline-block'],
bp: 1,
},
o: { prop: 'opacity', presets: ['0'], token: 'o', tokenClass: 1 },
v: { prop: 'visibility', presets: ['hidden'] },
ov: { prop: 'overflow', presets: ['hidden', 'auto', 'clip'] },
'ov-x': { prop: 'overflowX', presets: ['clip', 'auto', 'scroll'] },
'ov-y': { prop: 'overflowY', presets: ['clip', 'auto', 'scroll'] },
// overflow-clip-margin → safariで使えない
ar: {
prop: 'aspectRatio',
presets: ['21/9', '16/9', '3/2', '1/1'], // 4/3, 2/1
token: 'ar',
tokenClass: 1,
bp: 1,
},
// size
w: { prop: 'width', utils: { fit: 'fit-content' }, presets: ['100%'], token: 'sz', bp: 1 },
h: { prop: 'height', utils: { fit: 'fit-content' }, presets: ['100%'], token: 'sz', bp: 1 },
'min-w': { prop: 'minWidth', presets: ['100%'], token: 'sz', bp: 1 },
'max-w': { prop: 'maxWidth', presets: ['100%'], token: 'sz', bp: 1 },
'min-h': { prop: 'minHeight', presets: ['100%'], token: 'sz', bp: 1 },
'max-h': { prop: 'maxHeight', presets: ['100%'], token: 'sz', bp: 1 },
sz: { prop: 'inlineSize', token: 'sz' },
'min-sz': { prop: 'minInlineSize', token: 'sz' },
'max-sz': {
prop: 'maxInlineSize',
token: 'sz',
tokenClass: 1,
presets: ['full'],
exUtility: {
full: '',
bleed: '',
},
},
bsz: { prop: 'blockSize', token: 'sz' },
'min-bsz': { prop: 'minBlockSize', token: 'sz' },
'max-bsz': { prop: 'maxBlockSize', token: 'sz' },
// bg
bg: { prop: 'background', bp: 1 },
bgi: { prop: 'backgroundImage' },
bgr: { prop: 'backgroundRepeat', presets: ['no-repeat'] },
bgp: { prop: 'backgroundPosition', presets: ['center'] },
bgsz: { prop: 'backgroundSize', presets: ['cover', 'contain'] },
// bga: { prop: 'backgroundAttachment' }, // fixed
// bgo: { prop: 'backgroundOrigin' }, // border, padding, content
// bgblend: { prop: 'backgroundBlendMode' },
// bgclip: {
// prop: 'backgroundClip',
// presets: ['text'],
// },
bgc: {
prop: 'backgroundColor',
presets: ['base', 'base-2', 'text', 'brand', 'accent', 'inherit', 'transparent'],
token: 'color',
exUtility: { inherit: { 'background-color': 'inherit' } },
alwaysVar: 1,
},
c: {
// Note: bg系(bgclip)より後にくるように。
prop: 'color',
presets: ['base', 'text', 'text-2', 'brand', 'accent', 'inherit'],
token: 'color',
exUtility: {
inherit: { color: 'inherit' }, // --c ではなく color で出力したい
// mix: {'--_c1:currentColor;--_c2:transparent;--c:color-mix(in srgb, var(--_c1) var(--_mix-c, 50%), var(--_c2))'},
},
alwaysVar: 1,
},
keycolor: { isVar: 1, token: 'color' },
bd: { prop: 'border', presets: ['none'] },
bds: { isVar: 1, presets: ['dashed', 'dotted', 'double'] },
bdc: {
isVar: 1,
presets: ['brand', 'accent', 'divider', 'inherit', 'transparent'],
utils: { current: 'currentColor' },
token: 'color',
},
bdw: { isVar: 1, bp: 1 }, // --bdw のみ
'bd-x': { prop: 'borderInline' },
'bd-y': { prop: 'borderBlock' },
'bd-x-s': { prop: 'borderInlineStart' },
'bd-x-e': { prop: 'borderInlineEnd' },
'bd-y-s': { prop: 'borderBlockStart' },
'bd-y-e': { prop: 'borderBlockEnd' },
'bd-t': { prop: 'borderTop' },
'bd-b': { prop: 'borderBottom' },
'bd-l': { prop: 'borderLeft' },
'bd-r': { prop: 'borderRight' },
bdrs: {
prop: 'borderRadius',
presets: ['0'],
token: 'bdrs',
tokenClass: 1,
bp: 1,
alwaysVar: 1,
},
'bdrs-tl': { prop: 'borderTopLeftRadius', token: 'bdrs' },
'bdrs-tr': { prop: 'borderTopRightRadius', token: 'bdrs' },
'bdrs-br': { prop: 'borderBottomRightRadius', token: 'bdrs' },
'bdrs-bl': { prop: 'borderBottomLeftRadius', token: 'bdrs' },
'bdrs-ss': { prop: 'borderStartStartRadius', token: 'bdrs' },
'bdrs-se': { prop: 'borderStartEndRadius', token: 'bdrs' },
'bdrs-es': { prop: 'borderEndStartRadius', token: 'bdrs' },
'bdrs-ee': { prop: 'borderEndEndRadius', token: 'bdrs' },
bxsh: { prop: 'boxShadow', utils: { 0: 'none' }, token: 'bxsh', tokenClass: 1, bp: 1 },
// position
pos: {
prop: 'position',
presets: ['static', 'fixed', 'sticky', 'relative', 'absolute'],
},
z: { prop: 'zIndex', presets: ['-1', '0', '1', '99'] },
t: { prop: 'top', utils: { 0: '0%' }, presets: ['50%', '100%'], token: 'space' },
l: { prop: 'left', utils: { 0: '0%' }, presets: ['50%', '100%'], token: 'space' },
r: { prop: 'right', utils: { 0: '0%' }, presets: ['50%', '100%'], token: 'space' },
b: { prop: 'bottom', utils: { 0: '0%' }, presets: ['50%', '100%'], token: 'space' },
i: { prop: 'inset', utils: { 0: '0%' }, token: 'space' },
'i-x': { prop: 'insetInline', token: 'space' },
'i-y': { prop: 'insetBlock', token: 'space' },
'i-x-s': { prop: 'insetInlineStart', token: 'space' },
'i-x-e': { prop: 'insetInlineEnd', token: 'space' },
'i-y-s': { prop: 'insetBlockStart', token: 'space' },
'i-y-e': { prop: 'insetBlockEnd', token: 'space' },
// space
p: {
prop: 'padding',
presets: ['0'],
token: 'space',
tokenClass: 1,
alwaysVar: 1,
bp: 1,
},
px: { prop: 'paddingInline', presets: ['0'], token: 'space', tokenClass: 1, bp: 1 },
py: { prop: 'paddingBlock', presets: ['0'], token: 'space', tokenClass: 1, bp: 1 },
'px-s': { prop: 'paddingInlineStart', token: 'space', bp: 1 },
'px-e': { prop: 'paddingInlineEnd', token: 'space', bp: 1 },
'py-s': { prop: 'paddingBlockStart', token: 'space', bp: 1 },
'py-e': { prop: 'paddingBlockEnd', token: 'space', bp: 1 },
pl: { prop: 'paddingLeft', token: 'space', bp: 1 },
pr: { prop: 'paddingRight', token: 'space', bp: 1 },
pt: { prop: 'paddingTop', token: 'space', bp: 1 },
pb: { prop: 'paddingBottom', token: 'space', bp: 1 },
m: {
prop: 'margin',
presets: ['auto', '0'],
token: 'space',
tokenClass: 1,
alwaysVar: 1,
bp: 1,
},
mx: { prop: 'marginInline', presets: ['auto', '0'], token: 'space', tokenClass: 1, bp: 1 },
my: { prop: 'marginBlock', presets: ['auto', '0'], token: 'space', tokenClass: 1, bp: 1 },
'mx-s': { prop: 'marginInlineStart', presets: ['auto'], token: 'space', bp: 1 },
'mx-e': { prop: 'marginInlineEnd', presets: ['auto'], token: 'space', bp: 1 },
'my-s': { prop: 'marginBlockStart', token: 'space', bp: 1, presets: ['auto', '0'], tokenClass: 1 },
'my-e': { prop: 'marginBlockEnd', presets: ['auto'], token: 'space', bp: 1 },
ml: { prop: 'marginLeft', token: 'space', bp: 1 },
mr: { prop: 'marginRight', token: 'space', bp: 1 },
mt: { prop: 'marginTop', token: 'space', bp: 1 },
mb: { prop: 'marginBottom', token: 'space', bp: 1 },
g: {
prop: 'gap',
presets: ['0', 'inherit'],
exUtility: { inherit: { gap: 'inherit' } },
token: 'space',
tokenClass: 1,
bp: 1,
},
cg: { prop: 'columnGap', token: 'space', bp: 1 },
rg: { prop: 'rowGap', token: 'space', bp: 1 },
cols: { isVar: 1, bp: 1 },
rows: { isVar: 1, bp: 1 },
// flex
fxf: { prop: 'flexFlow' },
fxw: { prop: 'flexWrap', presets: ['wrap'], bp: 1 },
fxd: { prop: 'flexDirection', presets: ['column', 'column-reverse', 'row-reverse'], bp: 1 },
fx: { prop: 'flex', presets: ['1'], bp: 1 },
fxg: { prop: 'flexGrow', presets: ['1'] },
fxsh: { prop: 'flexShrink', presets: ['0'] },
fxb: { prop: 'flexBasis', bp: 1 },
// grid
// gd: { prop: 'grid' },
gt: {
prop: 'gridTemplate',
bp: 1,
},
gta: { prop: 'gridTemplateAreas', bp: 1 },
gtc: {
prop: 'gridTemplateColumns',
presets: ['subgrid'],
bp: 1,
},
gtr: {
prop: 'gridTemplateRows',
presets: ['subgrid'],
// exUtility: { repeat: { '--rows': '1', '--gtr': 'repeat(var(--rows), 1fr)' } },
bp: 1,
},
gaf: { prop: 'gridAutoFlow', presets: ['row', 'column'], bp: 1 }, //dense
gac: { prop: 'gridAutoColumns' },
gar: { prop: 'gridAutoRows' },
// grid item
ga: { prop: 'gridArea', utils: { '1/1': '1 / 1' }, bp: 1 },
gc: { prop: 'gridColumn', utils: { '1/-1': '1 / -1' }, bp: 1 },
gr: { prop: 'gridRow', utils: { '1/-1': '1 / -1' }, bp: 1 },
gcs: { prop: 'gridColumnStart' },
gce: { prop: 'gridColumnEnd' },
grs: { prop: 'gridRowStart' },
gre: { prop: 'gridRowEnd' },
// places
// -(ai|ac|ji|jc|aslf|jslf): / -$1:
ai: {
prop: 'alignItems',
presets: [...PLACE_PRESETS, 'stretch', ...PLACE_FX_PRESETS],
shorthands: PLACE_SHORTHANDS,
bp: 1,
},
ac: {
prop: 'alignContent',
presets: [...PLACE_PRESETS, ...PLACE_FX_PRESETS],
utils: { between: 'space-between' },
shorthands: PLACE_SHORTHANDS,
bp: 1,
},
ji: {
prop: 'justifyItems',
presets: [...PLACE_PRESETS, 'stretch', ...PLACE_FX_PRESETS],
shorthands: PLACE_SHORTHANDS,
bp: 1,
},
jc: {
prop: 'justifyContent',
presets: [...PLACE_PRESETS, ...PLACE_FX_PRESETS],
utils: { between: 'space-between' },
shorthands: PLACE_SHORTHANDS,
bp: 1,
},
pi: { prop: 'placeItems', presets: PLACE_PRESETS },
pc: { prop: 'placeContent', presets: PLACE_PRESETS },
aslf: {
prop: 'alignSelf',
presets: [...PLACE_PRESETS, 'stretch'],
shorthands: PLACE_SHORTHANDS,
},
jslf: {
prop: 'justifySelf',
presets: [...PLACE_PRESETS, 'stretch'],
shorthands: PLACE_SHORTHANDS,
},
pslf: { prop: 'placeSelf', presets: PLACE_PRESETS },
order: { prop: 'order', presets: ['0', '-1', '1'] },
// transform
// translate: {
// prop: 'translate',
// utils: {
// '-50X': '-50% 0',
// '-50Y': '0 -50%',
// '-50XY': '-50% -50%',
// },
// },
// rotate: {
// prop: 'rotate',
// utils: {
// [`45`]: '45deg',
// '-45': '-45deg',
// [`90`]: '90deg',
// '-90': '-90deg',
// // '180': '180deg',
// },
// },
// scale: {
// prop: 'scale',
// utils: {
// '-X': '-1 1',
// '-Y': '1 -1',
// '-XY': '-1 -1',
// },
// },
// others
ovw: { prop: 'overflowWrap', presets: ['anywhere'] },
whs: { prop: 'whiteSpace', presets: ['nowrap'] },
// wordbreak: { prop: 'wordBreak', utils: { keep: 'keep-all', all: 'break-all' } },
float: { prop: 'float', presets: ['left', 'right'] },
clear: { prop: 'clear', presets: ['both'] },
iso: { prop: 'isolation', presets: ['isolate'] },
wm: { prop: 'writingMode', presets: ['vertical-rl'], bp: 1 },
} as const; カスタマイズ例
例えば次のような設定ファイルを用意するとします。
import DEFAULT_CONFIG from 'lism-css/default-config';const { props, tokens } = DEFAULT_CONFIG;
export default { props: { // 既存propにpresetsを追加 ta: { presets: [...(props.ta.presets || []), 'justify'] }, // 既存propにutility値を追加 p: { utils: { box: '2em' } }, // 新しいpropの追加(filterはデフォルトに含まれない) filter: { utils: { blur: 'blur(3px)' } }, }, tokens: { // tokenClass:1 のpropは、tokens を追加するだけで自動でユーティリティ化される lts: [...(tokens.lts || []), '2xl'], }, traits: { // Trait(is--* / has--*)出力用のプロパティを追加 isHoge: 'is--hoge', },};上記のカスタマイズによって、Lismコンポーネントでは次のような挙動が追加されます。
ta='justify'→-ta:justifyを出力する。p='box'→-p:boxを出力する。filter='blur'→-filter:blurを出力する。lts='2xl'→-lts:2xlを出力する。isHoge→is--hogeを出力する。
<Box p="box" ta="justify" filter="blur" lts="2xl" isHoge>Box</Box>
↓ 出力結果
<div class="l--box is--hoge -p:box -ta:justify -filter:blur -lts:2xl">Box</div>ただし、lism.config.js でのカスタマイズは、コンポーネントからのHTML出力が変わるだけです。
追加されたユーティリティクラスに対するスタイルは読み込まれません。
追加スタイルを読み込ませる方法
lism.config.js で props を増やしただけでは、対応するユーティリティクラスのスタイルは存在しません。次のいずれかでスタイルを追加してください。
軽微な追加なら手書きで済ませる
カスタムトークンが少数で済むなら、CLI 再ビルドや SCSS 構成変更まで踏み込まず、Lism Props の :value 記法と global.css への手書きで十分です。
@layer lism-base { :root { --lts--2xl: 0.15em; }}
/* Property Class は @layer を付けない */.-lts\:2xl { letter-spacing: var(--lts--2xl);}<Text lts=":2xl">...</Text>トークンを体系的に拡張したい場合のみ、後述の CLI / SCSS 経由に切り替えるとよいでしょう。
lism-css のCLIコマンドでCSSビルドする
npx lism-css build を実行することで、lism.config.js でのカスタマイズの一部が反映した状態でlism-cssパッケージ内でスタイルのビルド処理が走ります。
今回の例だと、以下のスタイルがこのコマンドによって lism-css/main.css 内に自動で生成されます。
.-ta\:justify { text-align: justify;}.-p\:box { padding: 2em;}.-filter\:blur { filter: blur(3px);}.-lts\:2xl { letter-spacing: var(--lts--2xl);}ただし、ビルドが生成するのはあくまで .-lts\:2xl { letter-spacing: var(--lts--2xl) } のような トークン参照を含むユーティリティクラス までで、参照先の CSS 変数(--lts--2xl: 0.15em のようなトークンの 値そのもの)の定義は自動生成されません。is-- クラスのスタイルも同様です。
これらは別途、手動で定義して読み込ませる必要があります。
@layer lism-base { :root { --lts--2xl: 0.15em; }}
@layer lism-trait { .is--hoge { /* ... */ }}ビルド処理はパッケージを更新する度に再実行する必要があります。
パッケージのスタイルを再ビルドするコマンドのため、パッケージ更新ごとにビルドが必要です。
手動で追加する方法
ビルドコマンドを使わず、すべて手動でCSSを書いて読み込ませてももちろんOKです。
SCSSファイルへの追加上書き
lism.config.js にあわせて、SCSS変数を上書きしてから読み込ませてもよいでしょう。
// 設定カスタマイズ@use '../path-to/node_modules/lism-css/scss/setting' with ( $props: ( 'ta': ( utilities: ( 'justify': 'justify', ), ), 'p': ( utilities: ( 'box': '2em', ), ), 'filter': ( utilities: ( 'blur': 'blur(3px)', ), ), 'lts': ( utilities: ( '2xl': 'var(--lts--2xl)', ), ), ));
// lism の scssファイルを読み込む@use '../path-to/node_modules/lism-css/scss/main';
// トークン追記@layer lism-base { :root { --lts--2xl: 0.15em; }}