Lightning Web Components (以下、LWC) に対するスタイルの設定方法を、Aura コンポーネントの場合との比較を交えつつ、様々な例を挙げて紹介します。なお、この記事では Salesforce Platform 上 の LWC の開発を前提としています。
Lightning Design System の使用
Salesforce 標準のデザインに沿ったコンポーネントを作成するのであれば、Lightning Design System (LDS)を使用しましょう。カスタムの LWC は Aura と同じく自動的に LDS が適用されますので、LDS の CSS クラスを必要に応じて用いることができます。もちろん、自分でゼロから作り始める前に、Base Componentを組み合わせられないかは先に確認しましょう。
<template><!-- 例) slds-p-horizontal_small は左右にパディングを取る。
https://www.lightningdesignsystem.com/utilities/padding/ --><divclass="slds-p-horizontal_small">
Hello!
</div></template>
また、デザイントークンを使用することで、値のハードコーディングを避けることができます。先頭に --lwc-
を付与し、リンク先のドキュメントでケバブケースで表記されているトークンを、キャメルケースに置き換えればOKです。
.error{background-color:var(--lwc-colorBackgroundNotificationNew);color:var(--lwc-colorTextError);}
<template><spanclass="error">この文字は赤くなり背景は薄いグレーになります。</span></template>
ちなみに、Spring '20 から Aura で定義したカスタムのデザイントークンが使用できるようになるようです。
カスタムのスタイルを適用
コンポーネント毎に、同じ名称の CSS ファイルを1つだけ含めることができます。その CSS ファイルで定義されたスタイルは、そのコンポーネントに対してのみ適用されます。Aura と異なり THIS
CSS クラスを定義する必要はありません。
<template><h1>Hello LWC!</h1></template>
h1{font-size:2rem;color:rgb(0,112,210);}
親コンポーネントで定義されたスタイルは、子コンポーネントには継承されません。(→ Playground で結果を確認)
<template><h1>Hello LWC!</h1><c-child></c-child></template>
<template><h1>Hello Child!</h1><p>これは子コンポーネントです。</p></template>
/* child の h1 はそのまま */h1{font-size:2rem;color:rgb(0,112,210);}
ここで、c-child
に外枠を付けるにはどうしたらいいでしょうか。
h1{color:rgb(0,112,210);}/* よくない例 */c-child{display:block;border:1pxsolidrgb(200,200,200);}
上記のように、親の CSS に c-child
セレクタを定義することもできますが、親のスタイル定義に子のスタイル定義を混ぜてしまうと、見通しが悪くなりメンテナンス性にも欠けます。子コンポーネントのスタイルは、子コンポーネント側で定義しましょう。:host()
疑似クラスを用いると、そのコンポーネント自身(ホスト要素) にアクセスすることができます。
:host{display:block;border:1pxsolidrgb(200,200,200);}
下記のように、カッコとセレクタを使って、ホストのスタイルを状態に応じて変えることもできます。(→ Playgroundで結果を確認)
:host{display:block;border:1pxsolidrgb(200,200,200);}:host(.active){border-width:3px;}
<template><divclass="app"><h1>Hello LWC!</h1><c-child></c-child><c-childclass="active"></c-child><!--これだけ枠が太くなる--><c-child></c-child></div></template>
動的なスタイルの適用
例えば、チェックボックスにチェックを入れたら文字を薄くする、ボタンを押したら特定の要素を非表示にするなど、スタイルを動的に切り替えるにはいくつかの方法があります。
クラスのバインディング
まずはクラスを可変にする方法です。Aura では、マークアップ内に Expression (式)を用いて条件つきスタイルを定義することができました。
<aura:component><divclass="{!v.record.isActive__c ? 'slds-section slds-is-open' : 'slds-section'}"><!--中略--></div></aura:component>
LWC では、Aura のように式の中で演算子が利用できないため、JavaScript 側にロジックを記載する必要があります。
<template><divclass={sectionClass}><!--中略--></div></template>
getsectionClass(){//getter を定義returnthis.record.isActive__c?'slds-section slds-is-open':'slds-section';}
JavaScript に直接 CSS クラス名を書きたくない場合は、<template if:true|false={condition}>
で要素を切り替えることもできます。
<template><templateif:true={record.isActive__c}><!-- boolean を返す getter または property を用いる--><divclass="slds-section slds-is-open"><!--中略--></div></template><templateif:false={record.isActive__c}><divclass="slds-section"><!--中略--></div></template></template>
Aura に慣れていると、書きづらい、不便だと感じるかもしれませんが、LWC ではマークアップとロジックが必ず分離されるため安全だという見方ができます。
ループ内での動的なスタイル
ループ内の場合はもう少し工夫が必要です。例えば、Aura コンポーネントだと以下のような場合。
<aura:component><aura:attributetype="Contact[]"name="contacts"/><aura:iterationitems="{!v.contacts}"var="contact"><divclass="{!contact.isActive__c ? 'slds-section slds-is-open' : 'slds-section'}">...</div></aura:iteration></aura:component>
対応方法1: ループ内要素を子コンポーネント化し、ループアイテムを受けとる
スタイルやロジックは子コンポーネントで定義します。
<template><templatefor:each={contacts}for:item="contact"><c-childcontact={contact}key={contact.Id}></c-child></template></template>
<template><divclass={sectionClass}>...</div></template>
@apicontact;getsectionClass(){returnthis.contact.isActive__c?'slds-section slds-is-open':'slds-section';}
対応方法2: ループアイテムにプロパティを追加する
1コンポーネントで済ませたいのであればこちら。@wire
を使用している場合は、Property ではなく、Function として Apex を呼び出し、結果を加工すると良いでしょう。
<template><templatefor:each={contacts}for:item="contact"><divclass={contact.styleClass}key={contact.record.Id}>{contact.record.Name}</div></template></template>
@wire(getContacts)wiredContacts({error,data}){if(error){// エラー処理}elseif(data){this.contacts=data.map(contact=>{return{record:contact,styleClass:contact.isActive__c?'slds-section slds-is-open':'slds-section'}})}}
実行時のスタイルの追加と削除
DOM を操作してスタイルを制御することもできます。template.querySelector()
を用いて要素を特定し、classListからスタイルを追加・削除・切替します。Aura では、aura:id
と component.find()
でコンポーネントを特定して、 $A.util.addClass()
や $A.util.removeClass()
でクラスを制御しました。template.querySelector()
では id 属性によるクエリは非推奨のため注意が必要です。
<template><divid="first">First</div><divclass="unique-class-name">Second</div></template>
import{LightningElement}from'lwc';exportdefaultclassExampleextendsLightningElement{demoQuerySelector(){//querySelector の例this.template.querySelector('div');// OK. <div>First</div>this.template.querySelectorAll('div');// OK. [<div>First</div>, <div>Second</div>]this.template.querySelector('#first');// NGconstsecondDiv=this.template.querySelector('.unique-class-name');// OK. <div>Second</div>//スタイルを追加する例secondDiv.classList.add('slds-is-open');}}
比較
クラスのバインディング (式を使用したスタイルの適用) はコンポーネントの状態を利用できますが、実行時のスタイルの追加・削除 (DOM 操作) にはそれがありません。しかし、規模が大きい場合はパフォーマンスの観点で DOM 操作に分があるように思います。
ボタンをクリックした際にメッセージの表示を切り替える簡単な例で、書き方の違いを再確認しましょう。
Base Component の内部に対するスタイルの適用
Base Component の中身のスタイルを変更したいシーンがあります。例えば、以下のように、<lightning-textarea>
の高さを変更したい場合。
<template><div><h2class="header">テキストエリアの高さを変えたい</h2><lightning-textareaname="input1"label="テキストを入力"class="mytextarea"></lightning-textarea></div></template>
.mytextareatextarea{min-height:300px;}
上記の例では、テキストボックス自体の高さは指定した値になりませんが、これは期待通りの結果です。
<lightning-textarea>
の実装は Lightning Design System のドキュメントの通りですが、前述の通り、親コンポーネントで定義したスタイルは、子コンポーネントである <lightning-textarea>
内の要素には適用されません。
残念ながら、現状これに対するスマートな解決策はありませんが、実装可能な方法には以下があります。
対応方法1: Base Component をカスタムで再定義する
先のドキュメントに基づいて、Base Component を自ら作り直せば、好きなようにスタイルを設定できます。しかし、この方法でコンポーネントを様々なコンテキストで再利用するには、動的なスタイルの実装が増え煩雑になってしまうでしょう。
<divclass="slds-form-element"><labelclass="slds-form-element__label"for="textarea-id-01">Textarea Label</label><divclass="slds-form-element__control"><textareaid="textarea-id-01"class="slds-textarea your_own_class"></textarea></div></div>
対応方法2: style タグを差し込む
これは Base Component に限らない方法で、比較的簡易に実装できますが、標準のスタイルも上書きできてしまうため注意が必要です。
exportdefaultclassYourComponentextendsLightningElement{hasRendered=false;renderedCallback(){if(this.hasRendered)return;this.hasRendered=true;conststyle=document.createElement('style');style.innerText=`
c-your-component lightning-textarea textarea {
min-height: 300px;
}
`;this.template.querySelector('.mytextarea').appendChild(style);}}
対応方法3: 静的リソースの CSS を読み込んでスタイルを上書きする
ツリーを見ると、Salesforce Platform 上では LWC はネイティブの Shodow DOM を使っていないことがわかります。Synthetic Shadowと呼ばれる Polyfill を使用されており、ここではグローバルなCSSによるスタイルの上書きが可能です。これも予期しないスタイルの上書きには注意が必要です。
import{LightningElement}from'lwc';importcustomStylefrom'@salesforce/resourceUrl/your_custom_css';exportdefaultclassBaseComponentCssOverrideExampleextendsLightningElement{connectedCallback(){loadStyle(this,customStyle).then(()=>{});}}
.mytextareatextarea{min-height:300px;}
ちなみに、Shadow DOM の外側からスタイルを定義する方法として CSS Shadow Partsが W3C の Working Draft としてあり、主要なブラウザでサポートが進んでいます。「LWCのゴールは、LWCではなくなること」とのアナウンスもあり、遅かれ早かれ Salesforce Platform 上の LWC でもネイティブの Shadow DOM が使用されるようになり、この仕組みが利用できるようになることを期待しています。