Shadcn:
2026年4月9日resolvedTheme
ダークモードのテーマ取得
const { theme, setTheme, resolvedTheme } = useTheme();themeではなくresolvedThemeを使う。
themeをlogでみるとsystem/dark/lightの中から現在のthemeの文字列が取得される。systemというのはOSの設定に従うという意図の値であり、アプリ上の表示はダークモードかもしれないしライトモードかもしれない。systemという文字列を取得したところでdarkなのかlightなのか文字列としてわからないので、モードトグルボタンでは2回クリックが必要になる。一方、resolvedThemeの場合、logでみるとdark/lightの2択で表示されるのでこの問題は起こらない。
Breadcrumb
テンプレート:
"use client";
import React, { Fragment } from "react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbSeparator,
} from "@workspace/ui/components/breadcrumb";
import { usePathname } from "next/navigation";
import Link from "next/link";
const PostBreadcrumb = () => {
const pathname = usePathname();
const segments = pathname.split("/").filter(Boolean);
const breadcrumbs = segments.map((seg, idx) => ({
label: seg,
href: "/" + segments.slice(0, idx + 1).join("/"),
}));
return (
<>
<Breadcrumb>
<BreadcrumbList>
{breadcrumbs.map((item, index) => (
<Fragment key={index}>
<BreadcrumbItem>
<Link href={item.href}>{item.label}</Link>
</BreadcrumbItem>
{breadcrumbs.length - 1 !== index && <BreadcrumbSeparator />}
</Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
</>
);
};
export default PostBreadcrumb;状態
最初からAccordionをオープン
<Accordion
type="single"
collapsible
defaultValue="item-1"
>
<AccordionItem value="item-1"></AccordionItem>
</Accordion>;<Accordion defaultValue="item-1" >と<AccordionItem value="item-1">の値が一致していればデフォルトでOpenになる
// 複数Openにする場合
<Accordion
type="multiple"
defaultValue={["item-1", "item-3"]}
>
<AccordionItem value="item-1">...</AccordionItem>
<AccordionItem value="item-2">...</AccordionItem>
<AccordionItem value="item-3">...</AccordionItem>
</Accordion>data-[state=]:
ShadcnのTabやAccordionの開閉状態に応じてUIを変えたい場合、isOpenのような状態を作ってスタイルを管理する必要は無く、data-[state=] を使えば良い。
*デメリット:現状だと補完に出ない
className="data-[state=active]:accent-aqua"data-[state=active]data-[state=open / close]data-[state=checked]...
onOpenChange
const [contentOpen, setContentOpen] = useState(false)
<Collapsible open={contentOpen} onOpenChange={setContentOpen}>set関数のみ渡せば良い
Portal
Shadcnの開く系コンポーネントは基本的にReactの createPortal が使われている
createPortal:
親要素ではなく任意のDOM(body直下が多い)に表示する仕組み。検証ツールでみると開かれたコンポーネントが親タグから独立していることがわかる。
Portalを意識しないと意図しない挙動になる可能性があるため注意が必要
createPortal(<Modal />, document.body)DropdownMenuやPopoverの幅
DropdownMenuやPopover でWidthの制御が上手くできないのは、Portalが使われているので親要素を基準に出来ないのが原因
対策:固定値の [vw]や[px]
<DropdownMenuContent className="w-[300px]">
<PopoverContent className="w-[80vw]">formタグ
action属性を使ったserverActionの注意点:
name属性を付けたinputタグやselectタグが
DropdownMenuやPopoverの中にある場合、createPortalによりformタグの外にあるとみなされるので、action関数側でname属性のついた値を受け取れない。
HoverCardの発生が遅い
原因:デフォルトだと Delay がついている為
対策:delay
<HoverCard openDelay={10}>Shadcnは
HoverCardに限らずUXに関わるようなものは大抵Props経由で調整できる可能性があることを覚えておく
text-muted-foreground
主張しすぎないテキストには
text-gray-600等ではなく、text-muted-foregroundで事足りる。あるいはtext-foregroundのopacityを調節text-gray-600等だと、ダークモードのカラーも毎回書く手間が生じる。
Sidebar:tips
*Shadcn公式サイトのblocksから汎用的なSidebar一式をインストールした方が早い。
PCサイズの時にサイドバーを閉じた際にサイドバー自体が消えるのを避ける:
<Sidebar collapsible="icon">SidebarTriggerはどこに配置していい:
const Header = () => {
return (
<header className="sticky top-0 z-20 flex flex-col bg-background">
<SidebarTrigger className="sm:hidden" />開閉状態の取得:2パターン
const { state , open } = useSidebar();stateは
collapsed | epanded、openはboolean
group-data-[state=collapsed]:クライアントコンポーネント化しなくてよくなるが、
Sidebarタグ内でしか使えない。
Sidebarのサイズ変更:2パターン
sidebar.tsx内の定数を変更
const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_ICON = "3rem";SidebarをimportしているファイルでCSS変数を上書き
<div
style={
{
"--sidebar-width": "12rem",
"--sidebar": "var(--color-custom-sidebar)",
} as React.CSSProperties
}
>
<Sidebar collapsible="icon">Sidebarとmainコンテンツ:
const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<>
<SidebarProvider>
<SidebarTrigger className="md:hidden cursor-pointer" />
<AppSidebar />
<div className="bg-red-50 grow">{children}</div>
</SidebarProvider>
</>
);
};SidebarProviderの内に要素を入れないとレイアウトが崩れる
CSS変数の追加
:root {
--accent: oklch(20.86% 0.03034 222.592);
}
.dark {
--accent: oklch(59.799% 0.15604 274.86);
}
@theme inline {
--color-accent: var(--accent);
--color-accent-aqua: oklch(58% 0.095 222);
}ダーク/ライトを分ける意図がない場合、
:rootと.darkに書く必要はない
buttonVariants()
<Link
href={`/billing`}
className={buttonVariants({
variant: "secondary",
})}
>
<ExternalLink className="" size={14} />
<span>page</span>
</Link>buttonVariants()やbadgeVariantsをLinkタグ等に書くと、ButtonやBadgeでラップせずにそのスタイルを適用させられる。
Lucidアイコンの型
<Test Icon={Loader2} />
const Test = ({ Icon }: { Icon: LucideIcon }) => {
return <div>{<Icon />}</div>;
};