Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/lib/accordion/Accordion.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

let { children, flush, activeClass, inactiveClass, multiple = false, class: className, transitionType, ...restProps }: AccordionProps = $props();

const theme = getTheme("accordion");

// Simple reactive state object
const reactiveCtx: AccordionContextType = {
get flush() {
Expand All @@ -34,7 +32,7 @@
// Use untrack to explicitly capture only the initial value
createSingleSelectionContext(untrack(() => multiple));

const base = $derived(accordion({ flush, class: clsx(theme, className) }));
const base = $derived(accordion({ flush, class: clsx(getTheme("accordion"), className) }));
</script>

<div {...restProps} class={base}>
Expand Down
15 changes: 8 additions & 7 deletions src/lib/accordion/AccordionItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@
// Check if transitionType is explicitly set to undefined in props
const useTransition = $derived(transitionType === "none" ? false : ctxTransitionType === "none" ? false : true);

// Theme context
const theme = getTheme("accordionItem");

// single selection
const self = Symbol("accordion-item");

Expand All @@ -68,10 +65,14 @@
const { base, button, content, active, inactive } = $derived(accordionItem({ flush: ctx?.flush, open }));

let buttonClass = $derived(clsx(open && !ctx?.flush && (styling.active || ctx?.activeClass || active()), !open && !ctx?.flush && (styling.inactive || ctx?.inactiveClass || inactive())));

let baseClass = $derived(base({ class: clsx(getTheme("accordionItem")?.base, className) }));
let buttonCls = $derived(button({ class: clsx(buttonClass, getTheme("accordionItem")?.button, styling.button) }));
let contentCls = $derived(content({ class: clsx(getTheme("accordionItem")?.content, styling.content) }));
</script>

<h2 class={base({ class: clsx(theme?.base, className) })}>
<button type="button" onclick={handleToggle} class={button({ class: clsx(buttonClass, theme?.button, styling.button) })} aria-expanded={open}>
<h2 class={baseClass}>
<button type="button" onclick={handleToggle} class={buttonCls} aria-expanded={open}>
{#if header}
{@render header()}
{#if open}
Expand All @@ -96,14 +97,14 @@
{#if useTransition}
{#if open && transitionType !== "none"}
<div transition:transitionType={transitionParams as ParamsType}>
<div class={content({ class: clsx(theme?.content, styling.content) })}>
<div class={contentCls}>
{@render children()}
</div>
</div>
{/if}
{:else}
<div class={open ? "block" : "hidden"}>
<div class={content({ class: clsx(theme?.content, styling.content) })}>
<div class={contentCls}>
{@render children()}
</div>
</div>
Expand Down
5 changes: 1 addition & 4 deletions src/lib/alert/Alert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@
...restProps
}: AlertProps = $props();

// Theme context
const theme = getTheme("alert");

let divCls = $derived(
alert({
color,
rounded,
border,
icon: !!icon,
dismissable,
class: clsx(theme, className)
class: clsx(getTheme("alert"), className)
})
);

Expand Down
5 changes: 1 addition & 4 deletions src/lib/avatar/Avatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@

let { children, indicator, src, href, target, cornerStyle = "circular", border = false, stacked = false, dot, class: className, alt, size = "md", onclick, ...restProps }: AvatarProps = $props();

// Theme context
const theme = getTheme("avatar");

let dotProps = $derived(dot ? { placement: "top-right" as const, color: "gray" as const, size: "lg" as const, ...dot } : undefined);

let avatarClass = $derived(
Expand All @@ -18,7 +15,7 @@
border,
stacked,
size,
class: clsx(theme, className)
class: clsx(getTheme("avatar"), className)
})
);
</script>
Expand Down
7 changes: 2 additions & 5 deletions src/lib/badge/Badge.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@

const styling = $derived(classes ?? { linkClass: aClass });

// Theme context
const theme = getTheme("badge");

const { base, linkClass } = $derived(badge({ color, size: large ? "large" : "small", rounded, border }));

let ref: HTMLDivElement | undefined = $state(undefined);
Expand All @@ -52,9 +49,9 @@
</script>

{#if badgeStatus}
<div {...restProps} bind:this={ref} transition:transition={params as ParamsType} class={base({ class: clsx(theme?.base, className) })}>
<div {...restProps} bind:this={ref} transition:transition={params as ParamsType} class={base({ class: clsx(getTheme("badge")?.base, className) })}>
{#if href}
<a {href} {target} class={linkClass({ class: clsx(theme?.linkClass, styling.linkClass) })}>
<a {href} {target} class={linkClass({ class: clsx(getTheme("badge")?.linkClass, styling.linkClass) })}>
{@render children()}
</a>
{:else}
Expand Down
9 changes: 3 additions & 6 deletions src/lib/banner/Banner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@

const styling = $derived(classes ?? { insideDiv: innerClass, dismissable: closeClass });

// Theme context
const theme = getTheme("banner");

const { base, insideDiv, dismissable: dismissableClass } = $derived(banner({ type, color }));

let ref: HTMLDivElement | undefined = $state(undefined);
Expand All @@ -49,14 +46,14 @@
</script>

{#if open}
<div tabindex="-1" bind:this={ref} class={base({ class: clsx(theme?.base, className) })} {...restProps} transition:transition={params as ParamsType}>
<div class={insideDiv({ class: clsx(theme?.insideDiv, styling.insideDiv) })}>
<div tabindex="-1" bind:this={ref} class={base({ class: clsx(getTheme("banner")?.base, className) })} {...restProps} transition:transition={params as ParamsType}>
<div class={insideDiv({ class: clsx(getTheme("banner")?.insideDiv, styling.insideDiv) })}>
{@render children?.()}
</div>

{#if dismissable}
<div class="flex items-center justify-end">
<CloseButton class={dismissableClass({ class: clsx(theme?.dismissable, styling.dismissable) })} {color} ariaLabel="Remove banner" />
<CloseButton class={dismissableClass({ class: clsx(getTheme("banner")?.dismissable, styling.dismissable) })} {color} ariaLabel="Remove banner" />
</div>
{/if}
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/lib/bottom-navigation/BottomNav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@

const styling = $derived(classes ?? { inner: innerClass });

// Theme context
const theme = getTheme("bottomNav");

const activeCls = $derived(cn("text-primary-700 dark:text-primary-700 hover:text-primary-900 dark:hover:text-primary-900", activeClass));

// Create reactive context using getters
Expand All @@ -38,14 +35,17 @@
setBottomNavContext(reactiveCtx);

const { base, inner } = $derived(bottomNav({ position, navType }));

let baseClass = $derived(base({ class: clsx(getTheme("bottomNav")?.base, className ?? outerClass) }));
let innerClass2 = $derived(inner({ class: clsx(getTheme("bottomNav")?.inner, styling.inner) }));
</script>

<div {...restProps} class={base({ class: clsx(theme?.base, className ?? outerClass) })}>
<div {...restProps} class={baseClass}>
{#if header}
{@render header()}
{/if}

<div class={inner({ class: clsx(theme?.inner, styling.inner) })}>
<div class={innerClass2}>
{@render children()}
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/lib/bottom-navigation/BottomNavHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

const styling = $derived(classes ?? { innerDiv: innerClass });

// Theme context
const theme = getTheme("bottomNavHeader");

const { innerDiv, base } = $derived(bottomNavHeader());

let baseClass = $derived(base({ class: clsx(getTheme("bottomNavHeader")?.base, className ?? outerClass) }));
let innerDivClass = $derived(innerDiv({ class: clsx(getTheme("bottomNavHeader")?.innerDiv, styling.innerDiv) }));
</script>

<div {...restProps} class={base({ class: clsx(theme?.base, className ?? outerClass) })}>
<div class={innerDiv({ class: clsx(theme?.innerDiv, styling.innerDiv) })} role="group">
<div {...restProps} class={baseClass}>
<div class={innerDivClass} role="group">
{@render children()}
</div>
</div>
Expand Down
5 changes: 1 addition & 4 deletions src/lib/bottom-navigation/BottomNavHeaderItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@

let { itemName, active, class: className, ...restProps }: BottomNavHeaderItemProps = $props();

// Theme context
const theme = getTheme("bottomNavHeaderItem");

let base = $derived(bottomNavHeaderItem({ active, class: clsx(theme, className) }));
let base = $derived(bottomNavHeaderItem({ active, class: clsx(getTheme("bottomNavHeaderItem"), className) }));
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find getTheme function definition and check for memoization/caching

# Search for getTheme implementation
rg -nA 20 'export.*function\s+getTheme|export.*const\s+getTheme' --type ts --type tsx --type js --type jsx src/

# Also search more broadly
rg -nA 10 'getTheme' src/lib/theme --type ts --type tsx --type js --type jsx | head -100

Repository: themesberg/flowbite-svelte

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Search for themeUtils file
find src -name "themeUtils*" -type f

# Search for getTheme function definition
find src -name "*.svelte" -o -name "*.ts" -o -name "*.js" | xargs grep -l "export.*getTheme" 2>/dev/null | head -5

# Check the theme directory structure
ls -la src/lib/theme/ 2>/dev/null || echo "Theme dir not found"

Repository: themesberg/flowbite-svelte

Length of output: 552


🏁 Script executed:

#!/bin/bash
# Read themeUtils.ts to see getTheme implementation
cat -n src/lib/theme/themeUtils.ts

Repository: themesberg/flowbite-svelte

Length of output: 4357


🏁 Script executed:

#!/bin/bash
# Read BottomNavHeaderItem.svelte to see the exact usage pattern
cat -n src/lib/bottom-navigation/BottomNavHeaderItem.svelte

Repository: themesberg/flowbite-svelte

Length of output: 994


🏁 Script executed:

#!/bin/bash
# Check getThemeContext implementation
cat -n src/lib/context.ts

Repository: themesberg/flowbite-svelte

Length of output: 4806


🏁 Script executed:

#!/bin/bash
# Let's check how other components use getTheme to see if this pattern is widespread
rg -n 'let\s+\w+\s*=\s*\$derived.*getTheme' src/lib --type ts --type svelte -c | head -20

# Also check module-scope usage
rg -n 'const\s+\w+\s*=\s*getTheme' src/lib --type ts --type svelte -c | head -20

Repository: themesberg/flowbite-svelte

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Check how many components use getTheme in derived values
echo "=== getTheme inside \$derived values ==="
find src/lib -name "*.svelte" | xargs grep -l '\$derived.*getTheme' | wc -l

echo ""
echo "=== getTheme at module scope ==="
find src/lib -name "*.svelte" | xargs grep -l 'const.*=.*getTheme' | wc -l

echo ""
echo "=== Examples of getTheme in \$derived ==="
find src/lib -name "*.svelte" | xargs grep '\$derived.*getTheme' -B 2 -A 1 | head -40

Repository: themesberg/flowbite-svelte

Length of output: 3783


🏁 Script executed:

#!/bin/bash
# Check themes.ts to understand what getTheme returns
cat -n src/lib/theme/themes.ts | head -50

echo ""
echo "=== How is theme set in ThemeProvider? ==="
cat -n src/lib/theme/ThemeProvider.svelte

Repository: themesberg/flowbite-svelte

Length of output: 4558


🏁 Script executed:

#!/bin/bash
# Verify the computational cost of getTheme by checking what it actually does
# It's just context access + property lookup - let's confirm there's no hidden complexity

echo "=== Full getTheme implementation ==="
cat src/lib/theme/themeUtils.ts | head -15

echo ""
echo "=== Creating context ==="
grep -A 3 "getThemeContext" src/lib/context.ts | head -5

Repository: themesberg/flowbite-svelte

Length of output: 1077


Move getTheme call to module scope.

The inline getTheme("bottomNavHeaderItem") call executes on every recalculation of the derived base (triggered by changes to active or className). While getTheme is computationally cheap, this pattern is inconsistent with 108+ other components that correctly call it at module scope. The theme doesn't change dynamically within a component instance, so calling it inside $derived is unnecessary. Move to module scope: const theme = getTheme("bottomNavHeaderItem"); then use theme inside the derived.

This pattern affects 49 components across the PR.

πŸ€– Prompt for AI Agents
In src/lib/bottom-navigation/BottomNavHeaderItem.svelte around line 9, move the
getTheme("bottomNavHeaderItem") call out of the derived expression to module
scope to avoid re-running it on every derived recomputation: add a module-scope
constant e.g. const theme = getTheme("bottomNavHeaderItem"); then update the
derived to use that theme variable instead of calling getTheme inline (i.e.
replace getTheme("bottomNavHeaderItem") with theme).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it need to be reactive, getTheme("xxx"); needs to be inside $derived().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shinokada, thank you for the clarification! You're absolutely right. I understand now that this PR implements reactive theming where the theme can change dynamically at runtime, and calling getTheme inside $derived() ensures that components automatically re-render when the theme context updates.

The pattern $derived(fn({ class: clsx(getTheme("component"), className) })) correctly establishes reactive dependency on the theme context, which is exactly what's needed for the new reactive ThemeProvider implementation.

I apologize for the incorrect suggestion to move it to module scopeβ€”that would have broken the reactivity you're implementing.

  (\___/)
  (='.'=)
  (")_(")
*Learning noted!*

✏️ Learnings added
Learnt from: shinokada
Repo: themesberg/flowbite-svelte PR: 1882
File: src/lib/bottom-navigation/BottomNavHeaderItem.svelte:9-9
Timestamp: 2025-12-16T13:21:19.575Z
Learning: In flowbite-svelte, when using ThemeProvider with reactive themes, `getTheme("componentName")` calls must be placed inside `$derived()` expressions (not at module scope) to ensure components react to dynamic theme changes. This pattern is intentional for reactive theming support.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

</script>

<button {...restProps} class={base}>
Expand Down
7 changes: 2 additions & 5 deletions src/lib/bottom-navigation/BottomNavItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@

const styling = $derived(classes ?? { span: spanClass });

// Theme context
const theme = getTheme("bottomNavItem");

const context = getBottomNavContext();

let navUrl = $derived(context?.activeUrl || "");
Expand All @@ -39,11 +36,11 @@
});

function getCommonClass() {
return base({ class: clsx(isActive && (activeClass ?? context?.activeClass), theme?.base, className ?? btnClass) });
return base({ class: clsx(isActive && (activeClass ?? context?.activeClass), getTheme("bottomNavItem")?.base, className ?? btnClass) });
}

function getSpanClass() {
return span({ class: clsx(isActive && (activeClass ?? context?.activeClass), theme?.span, styling.span) });
return span({ class: clsx(isActive && (activeClass ?? context?.activeClass), getTheme("bottomNavItem")?.span, styling.span) });
}

/* eslint-disable @typescript-eslint/no-explicit-any */
Expand Down
6 changes: 2 additions & 4 deletions src/lib/breadcrumb/Breadcrumb.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@

const styling = $derived(classes ?? { list: olClass });

const theme = getTheme("breadcrumb");

const { base, list } = $derived(breadcrumb({ solid }));
let classNav = $derived(base({ class: clsx(theme?.base, className) }));
let classList = $derived(list({ class: clsx(theme?.list, styling.list) }));
let classNav = $derived(base({ class: clsx(getTheme("breadcrumb")?.base, className) }));
let classList = $derived(list({ class: clsx(getTheme("breadcrumb")?.list, styling.list) }));
</script>

<nav aria-label={ariaLabel} {...restProps} class={classNav}>
Expand Down
18 changes: 11 additions & 7 deletions src/lib/breadcrumb/BreadcrumbItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@

const styling = $derived(classes ?? {});

const theme = getTheme("breadcrumbItem");

const { base, separator } = $derived(
breadcrumbItem({
home,
hasHref: !!href
})
);

let liClass = $derived(base({ class: clsx(getTheme("breadcrumbItem")?.base, className) }));
let homeAnchorClass = $derived(base({ home: true, class: clsx(getTheme("breadcrumbItem")?.base, homeClass) }));
let linkAnchorClass = $derived(base({ home: false, hasHref: true, class: clsx(getTheme("breadcrumbItem")?.base, linkClass) }));
let spanElClass = $derived(base({ home: false, hasHref: false, class: clsx(getTheme("breadcrumbItem")?.base, spanClass) }));
let separatorClass = $derived(separator({ class: clsx(getTheme("breadcrumbItem")?.separator, styling.separator) }));
</script>

<li {...restProps} class={base({ class: clsx(theme?.base, className) })}>
<li {...restProps} class={liClass}>
{#if home}
<a class={base({ home: true, class: clsx(theme?.base, homeClass) })} {href}>
<a class={homeAnchorClass} {href}>
{#if icon}
{@render icon()}
{:else}
Expand All @@ -37,17 +41,17 @@
{#if icon}
{@render icon()}
{:else}
<svg class={separator({ class: clsx(theme?.separator, styling.separator) })} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<svg class={separatorClass} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
{/if}

{#if href}
<a class={base({ home: false, hasHref: true, class: clsx(theme?.base, linkClass) })} {href}>
<a class={linkAnchorClass} {href}>
{@render children()}
</a>
{:else}
<span class={base({ home: false, hasHref: false, class: clsx(theme?.base, spanClass) })}>
<span class={spanElClass}>
{@render children()}
</span>
{/if}
Expand Down
4 changes: 1 addition & 3 deletions src/lib/button-group/ButtonGroup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@

let { children, size = "md", disabled, class: className, ...restProps }: ButtonGroupProps = $props();

const theme = getTheme("buttonGroup");

let groupClass = $derived(buttonGroup({ size, class: clsx(theme, className) }));
let groupClass = $derived(buttonGroup({ size, class: clsx(getTheme("buttonGroup"), className) }));

// Create a reactive context object
// The object itself stays the same, but its properties are reactive
Expand Down
4 changes: 1 addition & 3 deletions src/lib/buttons/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@
...restProps
}: ButtonProps = $props();

const theme = getTheme("button");

let actualSize = $derived(group ? "sm" : size);
let actualColor = $derived(color ?? (group ? (outline ? "dark" : "alternative") : "primary"));
let isDisabled = $derived(Boolean(ctxDisabled) || Boolean(disabled) || loading);

const { base, outline: outline_, shadow: shadow_, spinner } = $derived(button({ color: actualColor, size: actualSize, disabled: isDisabled, pill, group: !!group }));

let btnCls = $derived(base({ class: clsx(outline && outline_(), shadow && shadow_(), theme?.base, className) }));
let btnCls = $derived(base({ class: clsx(outline && outline_(), shadow && shadow_(), getTheme("button")?.base, className) }));
</script>

{#if restProps.href !== undefined}
Expand Down
11 changes: 6 additions & 5 deletions src/lib/buttons/GradientButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@

let { children, outline, pill, color = "blue", shadow, class: className, href, disabled, size, btnClass, ...restProps }: GradientButtonProps = $props();

const theme = getTheme("gradientButton");

const { base, outlineWrapper } = $derived(gradientButton({ color, outline, pill, shadow, disabled, size, group: !!group }));

let baseClass = $derived(base({ class: clsx(getTheme("gradientButton")?.base, className) }));
let outlineWrapperClass = $derived(outlineWrapper({ class: clsx(getTheme("gradientButton")?.outlineWrapper, btnClass) }));
</script>

{#if outline}
<div class={base({ class: clsx(theme?.base, className) })}>
<Button {...restProps} class={outlineWrapper({ class: clsx(theme?.outlineWrapper, btnClass) })} {disabled} {href} {size}>
<div class={baseClass}>
<Button {...restProps} class={outlineWrapperClass} {disabled} {href} {size}>
{@render children?.()}
</Button>
</div>
{:else}
<Button {...restProps} class={base({ class: clsx(theme?.base, className) })} {disabled} {href} {size}>
<Button {...restProps} class={baseClass} {disabled} {href} {size}>
{@render children?.()}
</Button>
{/if}
Expand Down
12 changes: 7 additions & 5 deletions src/lib/card/Card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@

const styling = $derived(classes ?? { image: imgClass });

const theme = getTheme("card");

const { base, image } = $derived(
card({
size,
Expand All @@ -27,12 +25,16 @@
href: !!restProps.href
})
);

// Get theme reactively inside derived expressions
const baseClass = $derived(base({ class: clsx(getTheme("card")?.base, className) }));
const imageClass = $derived(image({ class: clsx(getTheme("card")?.image, styling.image) }));
</script>

{#snippet childSlot()}
{#if img}
<img
class={image({ class: clsx(theme?.image, styling.image) })}
class={imageClass}
src={img}
alt=""
loading="lazy"
Expand All @@ -50,11 +52,11 @@
{/snippet}

{#if restProps.href === undefined}
<div {...restProps} class={base({ class: clsx(theme?.base, className) })}>
<div {...restProps} class={baseClass}>
{@render childSlot()}
</div>
{:else}
<a {...restProps} class={base({ class: clsx(theme?.base, className) })}>
<a {...restProps} class={baseClass}>
{@render childSlot()}
</a>
{/if}
Expand Down
Loading