Changelog
Cherry Styled Components is currently at version 0.2.16. The entries below are based on Keep a Changelog, and the project adheres to Semantic Versioning.
Changed
- Breaking
StyledComponentsRegistrynow ships from thecherry-styled-components/nextsubpath instead of the package root. Update the import in your Next.jslayout.tsxtoimport { StyledComponentsRegistry } from "cherry-styled-components/next";. Every other export is unchanged and still comes fromcherry-styled-components. The registry is the only export that importsnext/navigation, and re-exporting it from the barrel forced every consumer to resolve Next just to import aButton: a plain Vite app importing nothing butButtonfailed to build withFailed to resolve import "next/navigation", andsideEffects: falsecould not save it because resolution happens before tree-shaking. Vite, CRA, Remix, Astro, and plain Rollup consumers now need no Next install and no bundler externals. See Installation - Dependency bumps:
lucide-react1.28.0 → 1.31.0,styled-components6.5.0 → 6.5.2,vite8.2.0 → 8.2.1,next16.3.0 → 16.3.1
useOnClickOutside(and with it Modal): a mousedown that no user press can account for is no longer read as a click outside while a native OS picker is engaged. iOS draws the pickers behind<select>and<input type="date|datetime-local|month|time|week">outside the web view, so the taps that drive them never reach the DOM; when the picker resolves, WebKit still emits a stray mousedown whose target sits outside the component, which dismissed the entire dialog the moment a user set a date inside aModalon iPhone. The hook now records genuine presses (pointerdown/touchstart, capture phase, one shared listener set for all instances) and ignores events that arrive with no press behind them while such a control holds focus or gave it up within the last 400ms. Backdrop taps, the close button, Esc, and clicks inside behave exactly as before
Fixed
createThemeInitScript: the first-visit dark branch now also rewrites<meta name="theme-color">(updating the existing tag or creating one, and pruning media-scoped duplicates, mirroringClientThemeProvider's sync), so browser chrome that tints from it — the Safari tab bar, Android Chrome's toolbar — no longer holds the light server-rendered color until hydration on a cookieless dark-OS visit from browsers withoutSec-CH-Prefers-Color-Scheme. The window is the whole load on a slow connection, and neither the server (no cookie to resolve) nor the provider's post-hydration effect can close it. A new optional second parameterdarkThemeColor(defaults todarkBackground, so existing calls keep tracking the page background like the provider's default$themeColor: "light") sets the value written; pass your dark theme's token value when the provider uses a non-default$themeColor, e.g.createThemeInitScript(themeDark.colors.light, themeDark.colors.primary)for$themeColor="primary". See Dark Mode
- Dependency bumps:
styled-components6.4.4 → 6.5.0
Changed
- ChatLauncher: the button is now pinned to the 30px compact header-control tier (
box-sizing: border-box; height: 30px, inner rowheight: 100%) instead of hugging its content, so its height cannot drift with platform font metrics and always matches neighboring header controls like the docs search button; rendered output is unchanged in browsers where the content already resolved to 30px
ChatProvider: opening the panel (launcher click, Command + I / Ctrl + I, orask()from a search modal) now commits the open withflushSyncand focuses the composer synchronously inside the triggering gesture. iOS Safari ignoresfocus()(and never raises the keyboard) once the tap gesture has passed, so the previous deferred-only focus opened the panel with an unfocused input on iPads and iPhones; the deferred focus remains as a fallback for lazily mounted composers- Prose: a top-level
.code-wrapperblock (an app's framed code component) now carries the block rhythm (10pxin$compact,20pxotherwise); previously only its innerprewas addressed, so framed code blocks sat flush against neighboring elements ChatSources: the chip row now separates itself from preceding content with a 10px top margin (the answer body trims its own trailing margin, so the chips sat flush against the last line); standalone first-child usage stays margin-free
Added
- Chat kit: a complete, transport-agnostic chat UI (
ChatProvider/useChat,ChatPanel,ChatMessageList,ChatMessage,ChatInput,ChatLauncher,ChatTyping,ChatSources). The provider is headless: it owns panel open/close state with focus capture and restore, the transcript, and streaming bookkeeping via anonSend(question, { signal, history, setAssistant })contract supplied by the app, so Cherry never fetches.ChatPanelrenders as a drawer, inline surface, or fullscreen, with dialog semantics below thelgbreakpoint (focus trap, inert siblings, Escape to close, body scroll lock), and Command + I (Ctrl + I on Windows) toggles the panel. See Chat - Opt-in
$showcasedemo mode onChatProvider: commands (help,list,callout,avatar,prose,sources,typing) are answered locally with live rendered element demos, no backend required - Opt-in
$glowtreatment on ChatInput and ChatLauncher: rotating rainbow border, inside-out focus/active rings matching Cherry's input state mechanics, ambient radiating glow, and sparkles Spinnercomponent: a rotating lucide icon (defaultLoaderCircle) with a 1s linear spin, disabled underprefers-reduced-motion. Its default color resolves fromtheme.colors.darkrather than inheritance, so it stays visible when the theme flips; an explicitcolorprop still wins. The chat send button now renders it in place of its local spin implementation. See Spinner- Supporting components Avatar, Callout, and Prose
useLockBodyScroll,useMediaQuery, anduseBelowBreakpointhooks, exported from the package root. See HooksthinScrollbarmixin for slim internal scroll areas;filledTextColoranddarkFilledTextRuleextracted fromButtoninto the shared mixins. See Mixins
- Chat drawer auto-scroll always lands on the last message: it re-arms with an instant jump whenever the panel opens or reopens, and follow re-engages on the rising edge of loading (a send) even if the reader had scrolled up
ChatLauncherglow transitions no longer shake: the rainbow accent is now three fixed-geometry gradient layers (1px hover, 2px pressed, 4px focus) sharing one conic gradient and cross-fading opacity, instead of a single band animating its geometry every frame- Chat polish: message avatars center on a one-line message and ride the visible bottom edge of replies taller than the scrollport; the composer receives focus each time an overlay panel opens;
Proseheadings use Cherry's typography mixins (shifted down two steps in$compactmode) instead of the browser's em-relative UA sizes
- Dependency bumps:
vite8.1.5 → 8.2.0,playwright-core1.62.0 → 1.62.1,@types/react19.2.17 → 19.2.18,@types/react-dom19.2.3 → 19.2.4
Added
- New
alpha(color, percent),shade(color, percent), andtint(color, percent)color helpers, exported from the package root.alphafades a color topercentopacity,shadedarkens itpercenttoward black, andtintlightens itpercenttoward white. They return a native CSScolor-mix(in srgb, ...)string rather than a computed hex, so the input can be any valid CSS color, including avar(--token)reference that only the browser can resolve. See Mixins
- The
polisheddependency.Button,IconButton,AvatarDropzone,Dropzone,Modal,Tabs,ThemeToggle, and theerrorInteractiveStylesmixin now derive their shades with the newalpha/shade/tinthelpers instead oflighten/darken/rgba, andpolishedis no longer listed as a build external. If your own code importedpolishedby way of Cherry's transitive dependency, install it directly
Button,IconButton,AvatarDropzone,Dropzone, andThemeTogglegained:root.darkselector fallbacks, so dark styling applies immediately from the pre-hydrationdarkclass on<html>instead of waiting for the theme object to swapClientThemeProvider: the htmldarkclass is no longer toggled before mount reconciliation has decided the real mode.themeInitScriptmay already have set the class from the cookie for the first paint, and syncing it against the not-yet-reconciled server theme stripped it for a frameClientThemeProvider:$themeColornow resolves CSS custom properties before writing thetheme-colormeta tag. Apps that theme through custom properties storevar(--token)in the theme object, and a meta tag cannot resolve that value itself
- Dependency bumps:
lucide-react1.27.0 → 1.28.0,@vitejs/plugin-react-swc4.3.2 → 4.3.3
Added
ThemeToggle: new$shortcutprop that binds the Command + Shift + L (Ctrl + Shift + L on Windows) keyboard shortcut while the toggle is mounted, flipping between the light and dark theme without a click. Off by default, so existing toggles are unaffected. Like the button's click handler it callstoggleThemefromThemeContext, and it stays a no-op until athemeDarkis passed to the provider. The listener is bound towindowand removed on unmount; it requires Shift and forbids Alt so it stays clear of the browser's Command + L (Ctrl + L on Windows) address-bar shortcut
Removed
- Breaking removed the custom
IconCheck,IconArrow, andIconCalendarexports. They were hand-rolled SVGs duplicating existing Lucide glyphs. The form controls that used them now render the built-inIconcomponent instead: the checkbox check mark usesCheck, theSelectdropdown arrow usesChevronDown, and the date/time calendar glyph usesCalendarDays. These icons now take their color fromcurrentColor(the theme's primary color, applied via CSS) rather than a hard-coded stroke. The checkbox check mark renders with a 6px stroke, and theSelectarrow now sizes to 24px to match the date/time calendar glyph. Replace any direct usage with<Icon name="Check" />,<Icon name="ChevronDown" />, or<Icon name="CalendarDays" />
Added
- AI assistant skill in
skills/cherry-design-system/: a Claude Agent Skill (SKILL.mdplusreferences/covering setup, theme tokens, the full component API, and recipes) that teaches LLMs to build with Cherry correctly, always using Cherry components for buttons and form controls, reading design values from the theme, and wiring the provider. Includes a portableAGENTS.mdfor other agents and points at the live docs (cherry.al/llms.txtand per-page.md). Documentation only, not part of the published npm package; install withnpx skills add cherry-design-system/styled-components
Accordion: the clickable title is now a native<button type="button">(with a button reset) instead of an<h3 role="button">, so it is keyboard-focusable and Enter/Space-activatable natively;aria-expandedis preserved, and a:focus-visibleoutline in the primary color was added for keyboard usersTabs: tab labels bumped tofont-weight: 700(was 600); the:focus-visiblering was retuned (cornerradius.xs→radius.lg, inset-4px→-2px)Input: removed the unusedchildrenprop fromInputProps.Inputrenders a void<input>, so any children were silently ignored; dropping it from the type turns that into a compile-time error
Fixed
Input: small-size date and time inputs now render the calendar icon at 18px instead of the default 24px, with the native::-webkit-calendar-picker-indicatorclick target repositioned to stay aligned with the visible icon
Fixed
Dropzone/AvatarDropzone: text now inherits the surrounding font family instead of pinning the theme'sfonts.text, matching thefont-family: inheritconvention used by Button, Input, Select, and Textarea. The root<button>elements setfont-family: inheritexplicitly, since buttons don't inherit fonts by default
Fixed
Toast: centered stacks no longer wrap their text prematurely. The notifications list is now a full-width strip (20px side margins) for every alignment instead of being anchored atleft: 50%, which capped its shrink-to-fit width at about half the viewport, most visibly on mobileToast: the notification strip and rows no longer swallow clicks on page content beside them;pointer-eventsis re-enabled only on the visible pill itself
Changed
- Hover styles apply on all devices again: removed the
@media (hover: hover)guards around&:hoverrules ininteractiveStyles,errorInteractiveStyles,Dropzone,AvatarDropzone,Tabs, andThemeToggle
Added
Tabs/TabContent: new tabbed-panels component with tablist/tab/tabpanel ARIA semantics, roving-tabindex arrow-key navigation (ArrowLeft/ArrowRight cycle, Home/End jump), and optional controlled selection viaactiveTab+onTabChange(defaultActiveTabfor uncontrolled use)IconButton:$activeprop for a toggle-like "on" state (primary border, translucent primary background); reflected asaria-pressedwhen set
ThemeToggle: swapped the scale hover/press effects for the sharedinteractiveStyleshover border + focus ring, and aligned the sun/moon icons exactly with the sliding knob
Added
Modal:stylepassthrough on the overlay root, complementingclassNamefor app-level restyling
Modal: corrected the restyling guidance in the prop docs.styled(Modal)cannot forward the$-props API, since styled-components strips transient props before they reach the wrapped component; restyle by passing aclassNameand targeting the class hooks instead
Added
Modal:$hideCloseButtonprop to omit the built-in close buttonModal:classNamepassthrough on the overlay root for app-level restyling; inner parts expose stable class hooks (.modal-inner,.modal-close,.modal-title,.modal-content)Modal:role="dialog",aria-modal="true", andaria-label(from$title) on the dialog surface
- Breaking-ish
Modalnow unmounts its children once the exit animation finishes instead of keeping them mounted (hidden) while closed. Children such as forms reset their state between openings. The enter/exit motion is unchanged visually (fade + 40px rise) but is now driven by keyframes instead of transitions useOnClickOutsidesubscribes its document listener once per mount (latest-ref pattern) instead of re-subscribing whenever callers pass inline ref arrays or callbacks
useOnClickOutsideignores unattached (null) refs instead of suppressing the callback entirely when any ref in the array is not mounted
Added
removeNotification(id)on the toast context, returned byuseToastNotificationsalongsideaddNotification; each toast innotificationsnow carries a uniqueid- Toast stack is an
aria-live="polite"region, so screen readers announce new toasts
- Toast internals reworked: state and variants are typed
$props on a newStyledNotificationIteminstead of className strings, each toast manages its own enter/exit lifecycle, and the space collapse animates the toast's real height viagrid-template-rowsinstead of amax-heightguess, so entering and exiting toasts glide instead of snapping - Bottom-anchored toasts (
$bottom) slide up from below instead of dropping down from above
- Dismissed and auto-hidden toasts are removed from state and the DOM after their exit animation instead of accumulating invisibly forever
Added
ThemeTogglecomponent: a pill-shaped sun/moon switch for light and dark modeClientThemeProvider: SSR-aware theme provider with flash-free dark mode. Renders the server-resolved theme on first paint, reconciles against thethemecookie and OS preference on mount, persists changes to cookie and localStorage (no API route needed), migrates legacy localStorage-only preferences, and keeps the htmldarkclass andtheme-colormeta tag in sync. Supports$initial,$themeColor, and$globalStylespropsthemeInitScriptandcreateThemeInitScript(darkBackground): blocking head script that seeds thethemecookie and prevents the dark-mode flash in browsers without color-scheme client hints (Safari, Firefox). Exported from a server-safe module so it can be used in server componentsresolveTheme(cookieValue, theme, themeDark)helper for resolving thethemecookie to a theme object in server codetoggleTheme()onThemeContext, provided by both theme providersinteractiveStylesanderrorInteractiveStylesmixins: hover/focus/active border + focus ring treatment for interactive surfaces, in the primary color and the error red respectively
Added
IconButtoncomponent for icon-only actionsPasswordcomponent with visibility toggleModalcomponentToastcomponent with theme-aware shadowsAccordioncomponentDropzoneandAvatarDropzonecomponents for file uploads
Added
- Responsive
$alignItems,$alignContent, and per-breakpoint$directionprops onFlex, backed by newgenerateAlignItemsStyles,generateAlignContentStyles, andgenerateDirectionStylesmixins
0.1.17 and earlier
Changes prior to 0.1.18 were not tracked in this changelog. See the git history for details.