ファランクスブログ

© 2026 all rights reserved.
  1. blog
  2. shadcn-ui
  • resolvedTheme
  • Breadcrumb
  • 状態
  • 最初からAccordionをオープン
  • data-[state=]:
  • onOpenChange
  • Portal
  • DropdownMenuやPopoverの幅
  • formタグ
  • HoverCardの発生が遅い
  • text-muted-foreground
  • Sidebar:tips
  • CSS変数の追加
  • buttonVariants()
  • Lucidアイコンの型

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>;
};

nextjs
/
other
  • resolvedTheme
  • Breadcrumb
  • 状態
  • 最初からAccordionをオープン
  • data-[state=]:
  • onOpenChange
  • Portal
  • DropdownMenuやPopoverの幅
  • formタグ
  • HoverCardの発生が遅い
  • text-muted-foreground
  • Sidebar:tips
  • CSS変数の追加
  • buttonVariants()
  • Lucidアイコンの型