[{"data":1,"prerenderedAt":1269},["ShallowReactive",2],{"navigation":3,"$f2vxmxe0ttlw1b":470,"\u002Fdocs\u002Fcomponents\u002Fmessage-scroller":481,"surround-\u002Fdocs\u002Fcomponents\u002Fmessage-scroller":1266},[4],{"title":5,"path":6,"stem":7,"children":8,"page":18},"Docs","\u002Fdocs","docs",[9,298,324,342,348,365,391,405],{"title":10,"path":11,"stem":12,"children":13,"new":18,"type":297},"Components","\u002Fdocs\u002Fcomponents","docs\u002F02.components",[14,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,205,209,213,217,221,225,229,233,237,241,245,249,253,257,261,265,269,273,277,281,285,289,293],{"title":15,"path":16,"stem":17,"new":18,"type":19,"children":-1},"Accordion","\u002Fdocs\u002Fcomponents\u002Faccordion","docs\u002Fcomponents\u002Faccordion",false,"component",{"title":21,"path":22,"stem":23,"new":18,"type":19,"children":-1},"Alert","\u002Fdocs\u002Fcomponents\u002Falert","docs\u002Fcomponents\u002Falert",{"title":25,"path":26,"stem":27,"new":18,"type":19,"children":-1},"Alert Dialog","\u002Fdocs\u002Fcomponents\u002Falert-dialog","docs\u002Fcomponents\u002Falert-dialog",{"title":29,"path":30,"stem":31,"new":18,"type":19,"children":-1},"Aspect Ratio","\u002Fdocs\u002Fcomponents\u002Faspect-ratio","docs\u002Fcomponents\u002Faspect-ratio",{"title":33,"path":34,"stem":35,"new":18,"type":19,"children":-1},"Attachment","\u002Fdocs\u002Fcomponents\u002Fattachment","docs\u002Fcomponents\u002Fattachment",{"title":37,"path":38,"stem":39,"new":18,"type":19,"children":-1},"Avatar","\u002Fdocs\u002Fcomponents\u002Favatar","docs\u002Fcomponents\u002Favatar",{"title":41,"path":42,"stem":43,"new":18,"type":19,"children":-1},"Badge","\u002Fdocs\u002Fcomponents\u002Fbadge","docs\u002Fcomponents\u002Fbadge",{"title":45,"path":46,"stem":47,"new":18,"type":19,"children":-1},"Breadcrumb","\u002Fdocs\u002Fcomponents\u002Fbreadcrumb","docs\u002Fcomponents\u002Fbreadcrumb",{"title":49,"path":50,"stem":51,"new":18,"type":19,"children":-1},"Bubble","\u002Fdocs\u002Fcomponents\u002Fbubble","docs\u002Fcomponents\u002Fbubble",{"title":53,"path":54,"stem":55,"new":18,"type":19,"children":-1},"Button","\u002Fdocs\u002Fcomponents\u002Fbutton","docs\u002Fcomponents\u002Fbutton",{"title":57,"path":58,"stem":59,"new":18,"type":19,"children":-1},"Button Group","\u002Fdocs\u002Fcomponents\u002Fbutton-group","docs\u002Fcomponents\u002Fbutton-group",{"title":61,"path":62,"stem":63,"new":18,"type":19,"children":-1},"Calendar","\u002Fdocs\u002Fcomponents\u002Fcalendar","docs\u002Fcomponents\u002Fcalendar",{"title":65,"path":66,"stem":67,"new":18,"type":19,"children":-1},"Card","\u002Fdocs\u002Fcomponents\u002Fcard","docs\u002Fcomponents\u002Fcard",{"title":69,"path":70,"stem":71,"new":18,"type":19,"children":-1},"Carousel","\u002Fdocs\u002Fcomponents\u002Fcarousel","docs\u002Fcomponents\u002Fcarousel",{"title":73,"path":74,"stem":75,"new":18,"type":19,"children":-1},"Chart","\u002Fdocs\u002Fcomponents\u002Fchart","docs\u002Fcomponents\u002Fchart",{"title":77,"path":78,"stem":79,"new":18,"type":19,"children":-1},"Checkbox","\u002Fdocs\u002Fcomponents\u002Fcheckbox","docs\u002Fcomponents\u002Fcheckbox",{"title":81,"path":82,"stem":83,"new":18,"type":19,"children":-1},"Collapsible","\u002Fdocs\u002Fcomponents\u002Fcollapsible","docs\u002Fcomponents\u002Fcollapsible",{"title":85,"path":86,"stem":87,"new":18,"type":19,"children":-1},"Combobox","\u002Fdocs\u002Fcomponents\u002Fcombobox","docs\u002Fcomponents\u002Fcombobox",{"title":89,"path":90,"stem":91,"new":18,"type":19,"children":-1},"Command","\u002Fdocs\u002Fcomponents\u002Fcommand","docs\u002Fcomponents\u002Fcommand",{"title":93,"path":94,"stem":95,"new":18,"type":19,"children":-1},"Context Menu","\u002Fdocs\u002Fcomponents\u002Fcontext-menu","docs\u002Fcomponents\u002Fcontext-menu",{"title":97,"path":98,"stem":99,"new":18,"type":19,"children":-1},"Data Table","\u002Fdocs\u002Fcomponents\u002Fdata-table","docs\u002Fcomponents\u002Fdata-table",{"title":101,"path":102,"stem":103,"new":18,"type":19,"children":-1},"Date Picker","\u002Fdocs\u002Fcomponents\u002Fdate-picker","docs\u002Fcomponents\u002Fdate-picker",{"title":105,"path":106,"stem":107,"new":18,"type":19,"children":-1},"Dialog","\u002Fdocs\u002Fcomponents\u002Fdialog","docs\u002Fcomponents\u002Fdialog",{"title":109,"path":110,"stem":111,"new":18,"type":19,"children":-1},"Drawer","\u002Fdocs\u002Fcomponents\u002Fdrawer","docs\u002Fcomponents\u002Fdrawer",{"title":113,"path":114,"stem":115,"new":18,"type":19,"children":-1},"Dropdown Menu","\u002Fdocs\u002Fcomponents\u002Fdropdown-menu","docs\u002Fcomponents\u002Fdropdown-menu",{"title":117,"path":118,"stem":119,"new":18,"type":19,"children":-1},"Empty","\u002Fdocs\u002Fcomponents\u002Fempty","docs\u002Fcomponents\u002Fempty",{"title":121,"path":122,"stem":123,"new":18,"type":19,"children":-1},"Field","\u002Fdocs\u002Fcomponents\u002Ffield","docs\u002Fcomponents\u002Ffield",{"title":125,"path":126,"stem":127,"new":18,"type":19,"children":-1},"Form","\u002Fdocs\u002Fcomponents\u002Fform","docs\u002Fcomponents\u002Fform",{"title":129,"path":130,"stem":131,"new":18,"type":19,"children":-1},"Hover Card","\u002Fdocs\u002Fcomponents\u002Fhover-card","docs\u002Fcomponents\u002Fhover-card",{"title":133,"path":134,"stem":135,"new":18,"type":19,"children":-1},"Input","\u002Fdocs\u002Fcomponents\u002Finput","docs\u002Fcomponents\u002Finput",{"title":137,"path":138,"stem":139,"new":18,"type":19,"children":-1},"Input Group","\u002Fdocs\u002Fcomponents\u002Finput-group","docs\u002Fcomponents\u002Finput-group",{"title":141,"path":142,"stem":143,"new":18,"type":19,"children":-1},"Input OTP","\u002Fdocs\u002Fcomponents\u002Finput-otp","docs\u002Fcomponents\u002Finput-otp",{"title":145,"path":146,"stem":147,"new":18,"type":19,"children":-1},"Item","\u002Fdocs\u002Fcomponents\u002Fitem","docs\u002Fcomponents\u002Fitem",{"title":149,"path":150,"stem":151,"new":18,"type":19,"children":-1},"Kbd","\u002Fdocs\u002Fcomponents\u002Fkbd","docs\u002Fcomponents\u002Fkbd",{"title":153,"path":154,"stem":155,"new":18,"type":19,"children":-1},"Label","\u002Fdocs\u002Fcomponents\u002Flabel","docs\u002Fcomponents\u002Flabel",{"title":157,"path":158,"stem":159,"new":18,"type":19,"children":-1},"Marker","\u002Fdocs\u002Fcomponents\u002Fmarker","docs\u002Fcomponents\u002Fmarker",{"title":161,"path":162,"stem":163,"new":18,"type":19,"children":-1},"Menubar","\u002Fdocs\u002Fcomponents\u002Fmenubar","docs\u002Fcomponents\u002Fmenubar",{"title":165,"path":166,"stem":167,"new":18,"type":19,"children":-1},"Message","\u002Fdocs\u002Fcomponents\u002Fmessage","docs\u002Fcomponents\u002Fmessage",{"title":169,"path":170,"stem":171,"new":18,"type":19,"children":-1},"Message Scroller","\u002Fdocs\u002Fcomponents\u002Fmessage-scroller","docs\u002Fcomponents\u002Fmessage-scroller",{"title":173,"path":174,"stem":175,"new":18,"type":19,"children":-1},"Native Select","\u002Fdocs\u002Fcomponents\u002Fnative-select","docs\u002Fcomponents\u002Fnative-select",{"title":177,"path":178,"stem":179,"new":18,"type":19,"children":-1},"Navigation Menu","\u002Fdocs\u002Fcomponents\u002Fnavigation-menu","docs\u002Fcomponents\u002Fnavigation-menu",{"title":181,"path":182,"stem":183,"new":18,"type":19,"children":-1},"Number Field","\u002Fdocs\u002Fcomponents\u002Fnumber-field","docs\u002Fcomponents\u002Fnumber-field",{"title":185,"path":186,"stem":187,"new":18,"type":19,"children":-1},"Pagination","\u002Fdocs\u002Fcomponents\u002Fpagination","docs\u002Fcomponents\u002Fpagination",{"title":189,"path":190,"stem":191,"new":18,"type":19,"children":-1},"Pin Input","\u002Fdocs\u002Fcomponents\u002Fpin-input","docs\u002Fcomponents\u002Fpin-input",{"title":193,"path":194,"stem":195,"new":18,"type":19,"children":-1},"Popover","\u002Fdocs\u002Fcomponents\u002Fpopover","docs\u002Fcomponents\u002Fpopover",{"title":197,"path":198,"stem":199,"new":18,"type":19,"children":-1},"Progress","\u002Fdocs\u002Fcomponents\u002Fprogress","docs\u002Fcomponents\u002Fprogress",{"title":201,"path":202,"stem":203,"new":204,"type":19,"children":-1},"Questionnaire","\u002Fdocs\u002Fcomponents\u002Fquestionnaire","docs\u002Fcomponents\u002Fquestionnaire",true,{"title":206,"path":207,"stem":208,"new":18,"type":19,"children":-1},"Radio Group","\u002Fdocs\u002Fcomponents\u002Fradio-group","docs\u002Fcomponents\u002Fradio-group",{"title":210,"path":211,"stem":212,"new":18,"type":19,"children":-1},"Range Calendar","\u002Fdocs\u002Fcomponents\u002Frange-calendar","docs\u002Fcomponents\u002Frange-calendar",{"title":214,"path":215,"stem":216,"new":18,"type":19,"children":-1},"Resizable","\u002Fdocs\u002Fcomponents\u002Fresizable","docs\u002Fcomponents\u002Fresizable",{"title":218,"path":219,"stem":220,"new":18,"type":19,"children":-1},"Scroll Area","\u002Fdocs\u002Fcomponents\u002Fscroll-area","docs\u002Fcomponents\u002Fscroll-area",{"title":222,"path":223,"stem":224,"new":18,"type":19,"children":-1},"Select","\u002Fdocs\u002Fcomponents\u002Fselect","docs\u002Fcomponents\u002Fselect",{"title":226,"path":227,"stem":228,"new":18,"type":19,"children":-1},"Separator","\u002Fdocs\u002Fcomponents\u002Fseparator","docs\u002Fcomponents\u002Fseparator",{"title":230,"path":231,"stem":232,"new":18,"type":19,"children":-1},"Sheet","\u002Fdocs\u002Fcomponents\u002Fsheet","docs\u002Fcomponents\u002Fsheet",{"title":234,"path":235,"stem":236,"new":18,"type":19,"children":-1},"Sidebar","\u002Fdocs\u002Fcomponents\u002Fsidebar","docs\u002Fcomponents\u002Fsidebar",{"title":238,"path":239,"stem":240,"new":18,"type":19,"children":-1},"Skeleton","\u002Fdocs\u002Fcomponents\u002Fskeleton","docs\u002Fcomponents\u002Fskeleton",{"title":242,"path":243,"stem":244,"new":18,"type":19,"children":-1},"Slider","\u002Fdocs\u002Fcomponents\u002Fslider","docs\u002Fcomponents\u002Fslider",{"title":246,"path":247,"stem":248,"new":18,"type":19,"children":-1},"Sonner","\u002Fdocs\u002Fcomponents\u002Fsonner","docs\u002Fcomponents\u002Fsonner",{"title":250,"path":251,"stem":252,"new":18,"type":19,"children":-1},"Spinner","\u002Fdocs\u002Fcomponents\u002Fspinner","docs\u002Fcomponents\u002Fspinner",{"title":254,"path":255,"stem":256,"new":18,"type":19,"children":-1},"Stepper","\u002Fdocs\u002Fcomponents\u002Fstepper","docs\u002Fcomponents\u002Fstepper",{"title":258,"path":259,"stem":260,"new":18,"type":19,"children":-1},"Switch","\u002Fdocs\u002Fcomponents\u002Fswitch","docs\u002Fcomponents\u002Fswitch",{"title":262,"path":263,"stem":264,"new":18,"type":19,"children":-1},"Table","\u002Fdocs\u002Fcomponents\u002Ftable","docs\u002Fcomponents\u002Ftable",{"title":266,"path":267,"stem":268,"new":18,"type":19,"children":-1},"Tabs","\u002Fdocs\u002Fcomponents\u002Ftabs","docs\u002Fcomponents\u002Ftabs",{"title":270,"path":271,"stem":272,"new":18,"type":19,"children":-1},"Tags Input","\u002Fdocs\u002Fcomponents\u002Ftags-input","docs\u002Fcomponents\u002Ftags-input",{"title":274,"path":275,"stem":276,"new":18,"type":19,"children":-1},"Textarea","\u002Fdocs\u002Fcomponents\u002Ftextarea","docs\u002Fcomponents\u002Ftextarea",{"title":278,"path":279,"stem":280,"new":18,"type":19,"children":-1},"Toast","\u002Fdocs\u002Fcomponents\u002Ftoast","docs\u002Fcomponents\u002Ftoast",{"title":282,"path":283,"stem":284,"new":18,"type":19,"children":-1},"Toggle","\u002Fdocs\u002Fcomponents\u002Ftoggle","docs\u002Fcomponents\u002Ftoggle",{"title":286,"path":287,"stem":288,"new":18,"type":19,"children":-1},"Toggle Group","\u002Fdocs\u002Fcomponents\u002Ftoggle-group","docs\u002Fcomponents\u002Ftoggle-group",{"title":290,"path":291,"stem":292,"new":18,"type":19,"children":-1},"Tooltip","\u002Fdocs\u002Fcomponents\u002Ftooltip","docs\u002Fcomponents\u002Ftooltip",{"title":294,"path":295,"stem":296,"new":18,"type":19,"children":-1},"Typography","\u002Fdocs\u002Fcomponents\u002Ftypography","docs\u002Fcomponents\u002Ftypography","group",{"title":299,"path":300,"stem":301,"children":302,"new":18,"type":297},"Installation","\u002Fdocs\u002Finstallation","docs\u002F02.installation",[303,308,312,316,320],{"title":304,"path":305,"stem":306,"new":18,"type":307,"children":-1},"Vite","\u002Fdocs\u002Finstallation\u002Fvite","docs\u002Finstallation\u002F01.vite","page",{"title":309,"path":310,"stem":311,"new":18,"type":307,"children":-1},"Nuxt","\u002Fdocs\u002Finstallation\u002Fnuxt","docs\u002Finstallation\u002F02.nuxt",{"title":313,"path":314,"stem":315,"new":18,"type":307,"children":-1},"Astro","\u002Fdocs\u002Finstallation\u002Fastro","docs\u002Finstallation\u002F03.astro",{"title":317,"path":318,"stem":319,"new":18,"type":307,"children":-1},"Laravel","\u002Fdocs\u002Finstallation\u002Flaravel","docs\u002Finstallation\u002F04.laravel",{"title":321,"path":322,"stem":323,"new":18,"type":307,"children":-1},"Manual Installation","\u002Fdocs\u002Finstallation\u002Fmanual","docs\u002Finstallation\u002F05.manual",{"title":325,"path":326,"stem":327,"children":328,"new":18,"type":297},"Dark Mode","\u002Fdocs\u002Fdark-mode","docs\u002F05.dark-mode",[329,332,335,339],{"title":304,"path":330,"stem":331,"new":18,"type":307,"children":-1},"\u002Fdocs\u002Fdark-mode\u002Fvite","docs\u002Fdark-mode\u002F01.vite",{"title":309,"path":333,"stem":334,"new":18,"type":307,"children":-1},"\u002Fdocs\u002Fdark-mode\u002Fnuxt","docs\u002Fdark-mode\u002F02.nuxt",{"title":336,"path":337,"stem":338,"new":18,"type":307,"children":-1},"Vitepress","\u002Fdocs\u002Fdark-mode\u002Fvitepress","docs\u002Fdark-mode\u002F03.vitepress",{"title":313,"path":340,"stem":341,"new":18,"type":307,"children":-1},"\u002Fdocs\u002Fdark-mode\u002Fastro","docs\u002Fdark-mode\u002F04.astro",{"title":343,"path":344,"stem":345,"children":346,"new":18,"type":297},"Directory","\u002Fdocs\u002Fdirectory","docs\u002Fdirectory\u002Findex",[347],{"title":343,"path":344,"stem":345,"new":18,"type":307,"children":-1},{"title":349,"path":350,"stem":351,"children":352,"new":18,"type":297},"Forms","\u002Fdocs\u002Fforms","docs\u002Fforms",[353,357,361],{"title":354,"path":355,"stem":356,"new":18,"type":307,"children":-1},"VeeValidate","\u002Fdocs\u002Fforms\u002Fvee-validate","docs\u002Fforms\u002F01.vee-validate",{"title":358,"path":359,"stem":360,"new":18,"type":307,"children":-1},"TanStack Form","\u002Fdocs\u002Fforms\u002Ftanstack-form","docs\u002Fforms\u002F02.tanstack-form",{"title":362,"path":363,"stem":364,"new":18,"type":307,"children":-1},"Formisch","\u002Fdocs\u002Fforms\u002Fformisch","docs\u002Fforms\u002F03.formisch",{"title":366,"path":367,"stem":368,"children":369,"new":18,"type":297},"Registry","\u002Fdocs\u002Fregistry","docs\u002Fregistry\u002Findex",[370,371,375,379,383,387],{"title":366,"path":367,"stem":368,"new":18,"type":307,"children":-1},{"title":372,"path":373,"stem":374,"new":18,"type":307,"children":-1},"Examples","\u002Fdocs\u002Fregistry\u002Fexamples","docs\u002Fregistry\u002Fexamples",{"title":376,"path":377,"stem":378,"new":18,"type":307,"children":-1},"FAQ","\u002Fdocs\u002Fregistry\u002Ffaq","docs\u002Fregistry\u002Ffaq",{"title":380,"path":381,"stem":382,"new":18,"type":307,"children":-1},"Getting Started","\u002Fdocs\u002Fregistry\u002Fgetting-started","docs\u002Fregistry\u002Fgetting-started",{"title":384,"path":385,"stem":386,"new":18,"type":307,"children":-1},"registry-item.json","\u002Fdocs\u002Fregistry\u002Fregistry-item-json","docs\u002Fregistry\u002Fregistry-item-json",{"title":388,"path":389,"stem":390,"new":18,"type":307,"children":-1},"registry.json","\u002Fdocs\u002Fregistry\u002Fregistry-json","docs\u002Fregistry\u002Fregistry-json",{"title":392,"path":393,"stem":394,"children":395,"new":18,"type":297},"Utilities","\u002Fdocs\u002Futilities","docs\u002Futilities\u002Findex",[396,397,401],{"title":392,"path":393,"stem":394,"new":18,"type":307,"children":-1},{"title":398,"path":399,"stem":400,"new":18,"type":307,"children":-1},"scroll-fade","\u002Fdocs\u002Futilities\u002Fscroll-fade","docs\u002Futilities\u002Fscroll-fade",{"title":402,"path":403,"stem":404,"new":18,"type":307,"children":-1},"shimmer","\u002Fdocs\u002Futilities\u002Fshimmer","docs\u002Futilities\u002Fshimmer",{"path":6,"stem":7,"title":406,"type":297,"children":407},"Get Started",[408,412,419,423,427,433,437,441,445,449,453,457,461,465],{"title":409,"path":410,"stem":411,"new":18,"type":307,"children":-1},"Introduction","\u002Fdocs\u002Fintroduction","docs\u002F01.introduction",{"title":299,"path":300,"stem":301,"children":413,"new":18,"type":297},[414,415,416,417,418],{"title":304,"path":305,"stem":306,"new":18,"type":307,"children":-1},{"title":309,"path":310,"stem":311,"new":18,"type":307,"children":-1},{"title":313,"path":314,"stem":315,"new":18,"type":307,"children":-1},{"title":317,"path":318,"stem":319,"new":18,"type":307,"children":-1},{"title":321,"path":322,"stem":323,"new":18,"type":307,"children":-1},{"title":420,"path":421,"stem":422,"new":18,"type":307,"children":-1},"components.json","\u002Fdocs\u002Fcomponents-json","docs\u002F03.components-json",{"title":424,"path":425,"stem":426,"new":18,"type":307,"children":-1},"Theming","\u002Fdocs\u002Ftheming","docs\u002F04.theming",{"title":325,"path":326,"stem":327,"children":428,"new":18,"type":297},[429,430,431,432],{"title":304,"path":330,"stem":331,"new":18,"type":307,"children":-1},{"title":309,"path":333,"stem":334,"new":18,"type":307,"children":-1},{"title":336,"path":337,"stem":338,"new":18,"type":307,"children":-1},{"title":313,"path":340,"stem":341,"new":18,"type":307,"children":-1},{"title":434,"path":435,"stem":436,"new":18,"type":307,"children":-1},"CLI","\u002Fdocs\u002Fcli","docs\u002F06.cli",{"title":438,"path":439,"stem":440,"new":18,"type":307,"children":-1},"JavaScript","\u002Fdocs\u002Fjavascript","docs\u002F07.javascript",{"title":442,"path":443,"stem":444,"new":18,"type":307,"children":-1},"RTL","\u002Fdocs\u002Frtl","docs\u002F08.rtl",{"title":446,"path":447,"stem":448,"new":18,"type":307,"children":-1},"Figma","\u002Fdocs\u002Ffigma","docs\u002F09.figma",{"title":450,"path":451,"stem":452,"new":18,"type":307,"children":-1},"Changelog","\u002Fdocs\u002Fchangelog","docs\u002F10.changelog",{"title":454,"path":455,"stem":456,"new":18,"type":307,"children":-1},"Legacy Docs","\u002Fdocs\u002Flegacy","docs\u002F11.legacy",{"title":458,"path":459,"stem":460,"new":18,"type":307,"children":-1},"Skills","\u002Fdocs\u002Fskills","docs\u002Fskills",{"title":462,"path":463,"stem":464,"new":18,"type":307,"children":-1},"Typeset","\u002Fdocs\u002Ftypeset","docs\u002Ftypeset",{"title":392,"path":393,"stem":394,"children":466,"new":18,"type":297},[467,468,469],{"title":392,"path":393,"stem":394,"new":18,"type":307,"children":-1},{"title":398,"path":399,"stem":400,"new":18,"type":307,"children":-1},{"title":402,"path":403,"stem":404,"new":18,"type":307,"children":-1},{"repo":471},{"id":472,"name":473,"repo":474,"description":475,"createdAt":476,"updatedAt":477,"pushedAt":478,"stars":479,"watchers":479,"forks":479,"defaultBranch":480},1360880822,"v-design","v-stella\u002Fv-design","An open-source Vue design system and component registry.","2026-09-08T03:28:46Z","2026-09-09T08:39:14Z","2026-09-09T08:36:47Z",0,"dev",{"id":482,"title":169,"body":483,"description":1259,"extension":1260,"links":1261,"meta":1262,"navigation":204,"new":18,"path":170,"rawbody":1263,"seo":1264,"stem":171,"__hash__":1265},"content\u002Fdocs\u002Fcomponents\u002Fmessage-scroller.md",{"type":484,"value":485,"toc":1232},"minimark",[486,495,500,508,516,519,574,578,587,590,594,602,606,611,623,629,632,636,640,643,647,651,658,662,666,676,680,684,691,695,699,710,714,718,728,732,736,747,753,757,761,776,780,784,790,794,798,801,812,934,937,971,986,990,1040,1044,1138,1153,1157,1162,1170,1199,1203,1209,1215,1219,1225],[487,488],"component-preview",{"className":489,"name":493,"previewClass":494},[490,491,492],"style-luma","rounded-[34px]","sm:rounded-4xl","MessageScrollerDemo","h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16",[496,497,499],"h2",{"id":498},"messagescroller","MessageScroller",[501,502,503,504,507],"p",{},"A great streaming chat scroller has to juggle a lot at once: pin to the live edge while a reply streams, but never fight a reader who scrolls up; anchor each new turn near the top with a peek of the previous exchange; preserve position when older history loads above; and expose commands to jump anywhere in the thread. ",[505,506,499],"code",{}," owns those hard parts so your message list doesn't have to.",[501,509,510,511,515],{},"It does ",[512,513,514],"strong",{},"not"," own your messages, AI state, transport, or model — it is a headless scroll container you compose around your own rows.",[496,517,299],{"id":518},"installation",[520,521,522,537,550],"code-tabs",{},[523,524,525,531],"tabs-list",{},[526,527,529],"tabs-trigger",{"value":528},"cli",[501,530,434],{},[526,532,534],{"value":533},"manual",[501,535,536],{},"Manual",[538,539,540],"tabs-content",{"value":528},[541,542,548],"pre",{"className":543,"code":545,"language":546,"meta":547},[544],"language-bash","npx shadcn-vue@latest add message-scroller\n","bash","",[505,549,545],{"__ignoreMap":547},[538,551,552],{"value":533},[553,554,555,569],"steps",{},[556,557,558],"step",{},[501,559,560,561,568],{},"Copy and paste the ",[562,563,567],"a",{"href":564,"rel":565},"https:\u002F\u002Fgithub.com\u002Funovue\u002Fshadcn-vue\u002Ftree\u002Fdev\u002Fapps\u002Fv4\u002Fregistry\u002Fbases\u002Freka\u002Fui\u002Fmessage-scroller",[566],"nofollow","GitHub source code"," into your project.",[556,570,571],{},[501,572,573],{},"Update the import paths to match your project setup.",[496,575,577],{"id":576},"usage","Usage",[541,579,585],{"className":580,"code":582,"language":583,"meta":584},[581],"language-vue","\u003Cscript setup lang=\"ts\">\nimport {\n  MessageScroller,\n  MessageScrollerButton,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerProvider,\n  MessageScrollerViewport,\n} from '@\u002Fcomponents\u002Fui\u002Fmessage-scroller'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CMessageScrollerProvider auto-scroll default-scroll-position=\"last-anchor\">\n    \u003CMessageScroller>\n      \u003CMessageScrollerViewport>\n        \u003CMessageScrollerContent>\n          \u003CMessageScrollerItem\n            v-for=\"message in messages\"\n            :key=\"message.id\"\n            :message-id=\"message.id\"\n            :scroll-anchor=\"message.role === 'user'\"\n          >\n            \u003C!-- Message \u002F Bubble \u002F Marker goes here -->\n          \u003C\u002FMessageScrollerItem>\n        \u003C\u002FMessageScrollerContent>\n      \u003C\u002FMessageScrollerViewport>\n      \u003CMessageScrollerButton direction=\"end\" \u002F>\n    \u003C\u002FMessageScroller>\n  \u003C\u002FMessageScrollerProvider>\n\u003C\u002Ftemplate>\n","vue","showLineNumbers",[505,586,582],{"__ignoreMap":547},[501,588,589],{},"The provider must have a constrained height (or a height-bounded parent) so the viewport can scroll.",[496,591,593],{"id":592},"composition","Composition",[541,595,600],{"className":596,"code":598,"language":599,"meta":547},[597],"language-text","MessageScrollerProvider\n└── MessageScroller\n    ├── MessageScrollerViewport\n    │   └── MessageScrollerContent\n    │       └── MessageScrollerItem\n    └── MessageScrollerButton\n","text",[505,601,598],{"__ignoreMap":547},[496,603,605],{"id":604},"core-concepts","Core Concepts",[607,608,610],"h3",{"id":609},"anchoring-turns","Anchoring Turns",[501,612,613,614,618,619,622],{},"A turn is the part of the conversation that starts a new exchange — usually the user's message and the assistant reply that follows. An ",[615,616,617],"em",{},"anchor"," is the row the viewport should treat as the start of that turn. Mark that row with ",[505,620,621],{},"scrollAnchor",". When a new anchor is appended, the viewport moves it near the top and keeps a peek of the previous item above it, so the new turn does not feel detached from its context.",[541,624,627],{"className":625,"code":626,"language":583,"meta":547},[581],"\u003CMessageScrollerItem\n  :message-id=\"message.id\"\n  :scroll-anchor=\"message.role === 'user'\"\n>\n  \u003C!-- ... -->\n\u003C\u002FMessageScrollerItem>\n",[505,628,626],{"__ignoreMap":547},[501,630,631],{},"Scroll anchors are not tied to message role. You can turn any row into an anchor: a user message, a system marker, a handoff event, or anything else that starts a meaningful turn.",[487,633],{"className":634,"name":635,"previewClass":494},[490,491,492],"MessageScrollerAnchoringDemo",[607,637,639],{"id":638},"group-chat","Group Chat",[501,641,642],{},"In a group chat, the turn boundary is often the message that asks the model to respond, or a marker like \"Marcus joined the chat\". Typing indicators and history controls usually should not anchor. Because anchoring is role-independent, you can anchor a marker just as easily as a message.",[487,644],{"className":645,"name":646,"previewClass":494},[490,491,492],"MessageScrollerGroupChatDemo",[607,648,650],{"id":649},"keeping-context-visible","Keeping Context Visible",[501,652,653,654,657],{},"When a new turn starts, it should still feel like part of the same continuous thread. ",[505,655,656],{},"scrollPreviousItemPeek"," keeps a slice of the previous item visible above the anchor, so the reader keeps their context instead of feeling like the conversation restarted on a blank page.",[487,659],{"className":660,"name":661,"previewClass":494},[490,491,492],"MessageScrollerPreviousContextDemo",[607,663,665],{"id":664},"following-the-live-edge","Following the Live Edge",[501,667,668,669,672,673,675],{},"When the reader is at the live edge, ",[505,670,671],{},"autoScroll"," keeps streamed replies in view as they grow. Scrolling away from the live edge — by wheel, touch, keyboard, or dragging the scrollbar — releases the view, so new chunks arrive without moving the reader. ",[505,674,671],{}," composes with turn anchoring: when a new turn anchors near the top, the view stays put while the reply streams into the room below it.",[487,677],{"className":678,"name":679,"previewClass":494},[490,491,492],"MessageScrollerStreamingDemo",[607,681,683],{"id":682},"opening-saved-threads","Opening Saved Threads",[501,685,686,687,690],{},"Reopening a saved thread at the absolute end often drops the reader in without enough context. A better default is ",[505,688,689],{},"\"last-anchor\"",": show the last meaningful turn, like the user's latest message, with the reply below it.",[487,692],{"className":693,"name":694,"previewClass":494},[490,491,492],"MessageScrollerOpeningPositionDemo",[607,696,698],{"id":697},"loading-earlier-messages","Loading Earlier Messages",[501,700,701,702,705,706,709],{},"Loading earlier messages should not move the conversation the reader is already looking at. When older rows are prepended above the current transcript, ",[505,703,704],{},"MessageScrollerViewport"," preserves the visible row so the reader stays in the same place while history loads above them. This is enabled by default through ",[505,707,708],{},"preserveScrollOnPrepend",".",[487,711],{"className":712,"name":713,"previewClass":494},[490,491,492],"MessageScrollerLoadHistoryDemo",[607,715,717],{"id":716},"animating-new-messages","Animating New Messages",[501,719,720,721,724,725,727],{},"A common chat pattern is to animate the user's message when it is sent, then let the assistant reply stream into a regular row below it. Keep ",[505,722,723],{},"messageId"," and ",[505,726,621],{}," on the animated item and use transform and opacity for the entrance — avoid animating height, margin, or padding, which can fight the scroller's positioning.",[487,729],{"className":730,"name":731,"previewClass":494},[490,491,492],"MessageScrollerAnimationDemo",[607,733,735],{"id":734},"jumping-to-messages","Jumping to Messages",[501,737,738,739,742,743,746],{},"Search results, permalinks, outline items, and toolbar buttons often need to drive the transcript from outside the message list. Use ",[505,740,741],{},"useMessageScroller"," for those controls — the composables read from ",[505,744,745],{},"MessageScrollerProvider",", so they work in any component inside the provider.",[541,748,751],{"className":749,"code":750,"language":583,"meta":547},[581],"\u003Cscript setup lang=\"ts\">\nimport { useMessageScroller } from '@\u002Fcomponents\u002Fui\u002Fmessage-scroller'\n\nconst { scrollToMessage, scrollToEnd, scrollToStart } = useMessageScroller()\n\u003C\u002Fscript>\n",[505,752,750],{"__ignoreMap":547},[487,754],{"className":755,"name":756,"previewClass":494},[490,491,492],"MessageScrollerCommandsDemo",[607,758,760],{"id":759},"tracking-the-readers-position","Tracking the Reader's Position",[501,762,763,764,767,768,771,772,775],{},"Use ",[505,765,766],{},"useMessageScrollerVisibility"," to track the reader's position — a table-of-contents or jump menu that highlights the current anchored turn. ",[505,769,770],{},"currentAnchorId"," answers \"where am I\" and stays set after that anchor scrolls above the viewport; ",[505,773,774],{},"visibleMessageIds"," answers \"what is on screen\", in document order.",[487,777],{"className":778,"name":779,"previewClass":494},[490,491,492],"MessageScrollerVisibilityDemo",[607,781,783],{"id":782},"reading-scroll-state","Reading Scroll State",[501,785,763,786,789],{},[505,787,788],{},"useMessageScrollerScrollable"," when you need scroll state in JavaScript, such as a status indicator or a custom \"jump to latest\" control. It reports which edges the viewport can still scroll toward.",[487,791],{"className":792,"name":793,"previewClass":494},[490,491,492],"MessageScrollerScrollableDemo",[496,795,797],{"id":796},"api-reference","API Reference",[607,799,745],{"id":800},"messagescrollerprovider",[501,802,803,804,807,808,811],{},"Owns the scroll state and behavior. Provide it via ",[505,805,806],{},"provide","\u002F",[505,809,810],{},"inject"," and expose the scroll composables to descendants.",[813,814,815,834],"table",{},[816,817,818],"thead",{},[819,820,821,825,828,831],"tr",{},[822,823,824],"th",{},"Prop",[822,826,827],{},"Type",[822,829,830],{},"Default",[822,832,833],{},"Description",[835,836,837,857,877,897,915],"tbody",{},[819,838,839,844,849,854],{},[840,841,842],"td",{},[505,843,671],{},[840,845,846],{},[505,847,848],{},"boolean",[840,850,851],{},[505,852,853],{},"false",[840,855,856],{},"Follow the live edge while the reader is pinned to the bottom.",[819,858,859,864,869,874],{},[840,860,861],{},[505,862,863],{},"defaultScrollPosition",[840,865,866],{},[505,867,868],{},"'start' | 'end' | 'last-anchor'",[840,870,871],{},[505,872,873],{},"'end'",[840,875,876],{},"Opening position for the transcript.",[819,878,879,884,889,894],{},[840,880,881],{},[505,882,883],{},"scrollEdgeThreshold",[840,885,886],{},[505,887,888],{},"number",[840,890,891],{},[505,892,893],{},"8",[840,895,896],{},"Distance in px from an edge before it is considered scrollable.",[819,898,899,903,907,912],{},[840,900,901],{},[505,902,656],{},[840,904,905],{},[505,906,888],{},[840,908,909],{},[505,910,911],{},"64",[840,913,914],{},"Amount in px of the previous turn kept visible when anchoring.",[819,916,917,922,926,931],{},[840,918,919],{},[505,920,921],{},"scrollMargin",[840,923,924],{},[505,925,888],{},[840,927,928],{},[505,929,930],{},"0",[840,932,933],{},"Extra offset in px applied when scrolling to an element.",[607,935,704],{"id":936},"messagescrollerviewport",[813,938,939,951],{},[816,940,941],{},[819,942,943,945,947,949],{},[822,944,824],{},[822,946,827],{},[822,948,830],{},[822,950,833],{},[835,952,953],{},[819,954,955,959,963,968],{},[840,956,957],{},[505,958,708],{},[840,960,961],{},[505,962,848],{},[840,964,965],{},[505,966,967],{},"true",[840,969,970],{},"Keep the current view when messages are added above.",[501,972,973,974,977,978,981,982,985],{},"Rendered as a ",[505,975,976],{},"role=\"region\"",", ",[505,979,980],{},"aria-label=\"Messages\"",", focusable (",[505,983,984],{},"tabindex=\"0\"",") native scroll container.",[607,987,989],{"id":988},"messagescrolleritem","MessageScrollerItem",[813,991,992,1004],{},[816,993,994],{},[819,995,996,998,1000,1002],{},[822,997,824],{},[822,999,827],{},[822,1001,830],{},[822,1003,833],{},[835,1005,1006,1023],{},[819,1007,1008,1012,1017,1020],{},[840,1009,1010],{},[505,1011,723],{},[840,1013,1014],{},[505,1015,1016],{},"string",[840,1018,1019],{},"—",[840,1021,1022],{},"Stable id used for anchoring, visibility, jumps.",[819,1024,1025,1029,1033,1037],{},[840,1026,1027],{},[505,1028,621],{},[840,1030,1031],{},[505,1032,848],{},[840,1034,1035],{},[505,1036,853],{},[840,1038,1039],{},"Marks this row as the start of a turn.",[607,1041,1043],{"id":1042},"messagescrollerbutton","MessageScrollerButton",[813,1045,1046,1058],{},[816,1047,1048],{},[819,1049,1050,1052,1054,1056],{},[822,1051,824],{},[822,1053,827],{},[822,1055,830],{},[822,1057,833],{},[835,1059,1060,1079,1099,1119],{},[819,1061,1062,1067,1072,1076],{},[840,1063,1064],{},[505,1065,1066],{},"direction",[840,1068,1069],{},[505,1070,1071],{},"'start' | 'end'",[840,1073,1074],{},[505,1075,873],{},[840,1077,1078],{},"Direction the button scrolls toward.",[819,1080,1081,1086,1091,1096],{},[840,1082,1083],{},[505,1084,1085],{},"behavior",[840,1087,1088],{},[505,1089,1090],{},"ScrollBehavior",[840,1092,1093],{},[505,1094,1095],{},"'smooth'",[840,1097,1098],{},"Scroll behavior for the jump.",[819,1100,1101,1106,1111,1116],{},[840,1102,1103],{},[505,1104,1105],{},"variant",[840,1107,1108],{},[505,1109,1110],{},"ButtonVariants",[840,1112,1113],{},[505,1114,1115],{},"'secondary'",[840,1117,1118],{},"Button variant.",[819,1120,1121,1126,1130,1135],{},[840,1122,1123],{},[505,1124,1125],{},"size",[840,1127,1128],{},[505,1129,1110],{},[840,1131,1132],{},[505,1133,1134],{},"'icon-sm'",[840,1136,1137],{},"Button size.",[501,1139,1140,1141,1144,1145,1148,1149,1152],{},"Exposes ",[505,1142,1143],{},"data-active"," for styling and becomes ",[505,1146,1147],{},"inert"," with ",[505,1150,1151],{},"tabindex=\"-1\""," when there is nothing to scroll toward.",[607,1154,1156],{"id":1155},"composables","Composables",[1158,1159,1161],"h4",{"id":1160},"usemessagescroller","useMessageScroller()",[541,1163,1168],{"className":1164,"code":1166,"language":1167,"meta":547},[1165],"language-ts","const { scrollToMessage, scrollToEnd, scrollToStart } = useMessageScroller()\n","ts",[505,1169,1166],{"__ignoreMap":547},[1171,1172,1173,1189],"ul",{},[1174,1175,1176,1179,1180,1182,1183,1185,1186,1188],"li",{},[505,1177,1178],{},"scrollToMessage(id, options?)"," — scroll to the item with the matching ",[505,1181,723],{},". Returns ",[505,1184,967],{}," if handled (queued if the item is not mounted yet), ",[505,1187,853],{}," if the id is missing after rows have mounted.",[1174,1190,1191,1194,1195,1198],{},[505,1192,1193],{},"scrollToEnd(options?)"," \u002F ",[505,1196,1197],{},"scrollToStart(options?)"," — scroll to the live edge or the top.",[1158,1200,1202],{"id":1201},"usemessagescrollervisibility","useMessageScrollerVisibility()",[541,1204,1207],{"className":1205,"code":1206,"language":1167,"meta":547},[1165],"const visibility = useMessageScrollerVisibility()\n\u002F\u002F visibility.value.currentAnchorId, visibility.value.visibleMessageIds\n",[505,1208,1206],{"__ignoreMap":547},[501,1210,1211,1212,1214],{},"Tracking only runs while something subscribes, and rows need a ",[505,1213,723],{}," to participate.",[1158,1216,1218],{"id":1217},"usemessagescrollerscrollable","useMessageScrollerScrollable()",[541,1220,1223],{"className":1221,"code":1222,"language":1167,"meta":547},[1165],"const scrollable = useMessageScrollerScrollable()\n\u002F\u002F scrollable.value.start, scrollable.value.end\n",[505,1224,1222],{"__ignoreMap":547},[501,1226,1227,1228,1231],{},"Reports which edges the viewport can still scroll toward. For styling the scroller itself, prefer the ",[505,1229,1230],{},"data-scrollable"," attribute.",{"title":547,"searchDepth":1233,"depth":1233,"links":1234},2,[1235,1236,1237,1238,1239,1252],{"id":498,"depth":1233,"text":499},{"id":518,"depth":1233,"text":299},{"id":576,"depth":1233,"text":577},{"id":592,"depth":1233,"text":593},{"id":604,"depth":1233,"text":605,"children":1240},[1241,1243,1244,1245,1246,1247,1248,1249,1250,1251],{"id":609,"depth":1242,"text":610},3,{"id":638,"depth":1242,"text":639},{"id":649,"depth":1242,"text":650},{"id":664,"depth":1242,"text":665},{"id":682,"depth":1242,"text":683},{"id":697,"depth":1242,"text":698},{"id":716,"depth":1242,"text":717},{"id":734,"depth":1242,"text":735},{"id":759,"depth":1242,"text":760},{"id":782,"depth":1242,"text":783},{"id":796,"depth":1233,"text":797,"children":1253},[1254,1255,1256,1257,1258],{"id":800,"depth":1242,"text":745},{"id":936,"depth":1242,"text":704},{"id":988,"depth":1242,"text":989},{"id":1042,"depth":1242,"text":1043},{"id":1155,"depth":1242,"text":1156},"A scroll container for chat transcripts that anchors turns, follows streamed replies, restores prepended history, and jumps to messages.","md",null,{"component":204},"---\ntitle: Message Scroller\ndescription: A scroll container for chat transcripts that anchors turns, follows streamed replies, restores prepended history, and jumps to messages.\ncomponent: true\n---\n\n::component-preview\n---\nname: MessageScrollerDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n## MessageScroller\n\nA great streaming chat scroller has to juggle a lot at once: pin to the live edge while a reply streams, but never fight a reader who scrolls up; anchor each new turn near the top with a peek of the previous exchange; preserve position when older history loads above; and expose commands to jump anywhere in the thread. `MessageScroller` owns those hard parts so your message list doesn't have to.\n\nIt does **not** own your messages, AI state, transport, or model — it is a headless scroll container you compose around your own rows.\n\n## Installation\n\n:::::code-tabs\n\n:::tabs-list\n\n  ::tabs-trigger{value=\"cli\"}\n  CLI\n  ::\n\n  ::tabs-trigger{value=\"manual\"}\n  Manual\n  ::\n\n:::\n\n::tabs-content{value=\"cli\"}\n\n```bash\nnpx shadcn-vue@latest add message-scroller\n```\n\n::\n\n::::tabs-content{value=\"manual\"}\n  :::steps\n    ::step\n    Copy and paste the [GitHub source code](https:\u002F\u002Fgithub.com\u002Funovue\u002Fshadcn-vue\u002Ftree\u002Fdev\u002Fapps\u002Fv4\u002Fregistry\u002Fbases\u002Freka\u002Fui\u002Fmessage-scroller) into your project.\n    ::\n\n    ::step\n    Update the import paths to match your project setup.\n    ::\n  :::\n::::\n\n:::::\n\n## Usage\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport {\n  MessageScroller,\n  MessageScrollerButton,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerProvider,\n  MessageScrollerViewport,\n} from '@\u002Fcomponents\u002Fui\u002Fmessage-scroller'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CMessageScrollerProvider auto-scroll default-scroll-position=\"last-anchor\">\n    \u003CMessageScroller>\n      \u003CMessageScrollerViewport>\n        \u003CMessageScrollerContent>\n          \u003CMessageScrollerItem\n            v-for=\"message in messages\"\n            :key=\"message.id\"\n            :message-id=\"message.id\"\n            :scroll-anchor=\"message.role === 'user'\"\n          >\n            \u003C!-- Message \u002F Bubble \u002F Marker goes here -->\n          \u003C\u002FMessageScrollerItem>\n        \u003C\u002FMessageScrollerContent>\n      \u003C\u002FMessageScrollerViewport>\n      \u003CMessageScrollerButton direction=\"end\" \u002F>\n    \u003C\u002FMessageScroller>\n  \u003C\u002FMessageScrollerProvider>\n\u003C\u002Ftemplate>\n```\n\nThe provider must have a constrained height (or a height-bounded parent) so the viewport can scroll.\n\n## Composition\n\n```text\nMessageScrollerProvider\n└── MessageScroller\n    ├── MessageScrollerViewport\n    │   └── MessageScrollerContent\n    │       └── MessageScrollerItem\n    └── MessageScrollerButton\n```\n\n## Core Concepts\n\n### Anchoring Turns\n\nA turn is the part of the conversation that starts a new exchange — usually the user's message and the assistant reply that follows. An *anchor* is the row the viewport should treat as the start of that turn. Mark that row with `scrollAnchor`. When a new anchor is appended, the viewport moves it near the top and keeps a peek of the previous item above it, so the new turn does not feel detached from its context.\n\n```vue\n\u003CMessageScrollerItem\n  :message-id=\"message.id\"\n  :scroll-anchor=\"message.role === 'user'\"\n>\n  \u003C!-- ... -->\n\u003C\u002FMessageScrollerItem>\n```\n\nScroll anchors are not tied to message role. You can turn any row into an anchor: a user message, a system marker, a handoff event, or anything else that starts a meaningful turn.\n\n::component-preview\n---\nname: MessageScrollerAnchoringDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Group Chat\n\nIn a group chat, the turn boundary is often the message that asks the model to respond, or a marker like \"Marcus joined the chat\". Typing indicators and history controls usually should not anchor. Because anchoring is role-independent, you can anchor a marker just as easily as a message.\n\n::component-preview\n---\nname: MessageScrollerGroupChatDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Keeping Context Visible\n\nWhen a new turn starts, it should still feel like part of the same continuous thread. `scrollPreviousItemPeek` keeps a slice of the previous item visible above the anchor, so the reader keeps their context instead of feeling like the conversation restarted on a blank page.\n\n::component-preview\n---\nname: MessageScrollerPreviousContextDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Following the Live Edge\n\nWhen the reader is at the live edge, `autoScroll` keeps streamed replies in view as they grow. Scrolling away from the live edge — by wheel, touch, keyboard, or dragging the scrollbar — releases the view, so new chunks arrive without moving the reader. `autoScroll` composes with turn anchoring: when a new turn anchors near the top, the view stays put while the reply streams into the room below it.\n\n::component-preview\n---\nname: MessageScrollerStreamingDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Opening Saved Threads\n\nReopening a saved thread at the absolute end often drops the reader in without enough context. A better default is `\"last-anchor\"`: show the last meaningful turn, like the user's latest message, with the reply below it.\n\n::component-preview\n---\nname: MessageScrollerOpeningPositionDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Loading Earlier Messages\n\nLoading earlier messages should not move the conversation the reader is already looking at. When older rows are prepended above the current transcript, `MessageScrollerViewport` preserves the visible row so the reader stays in the same place while history loads above them. This is enabled by default through `preserveScrollOnPrepend`.\n\n::component-preview\n---\nname: MessageScrollerLoadHistoryDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Animating New Messages\n\nA common chat pattern is to animate the user's message when it is sent, then let the assistant reply stream into a regular row below it. Keep `messageId` and `scrollAnchor` on the animated item and use transform and opacity for the entrance — avoid animating height, margin, or padding, which can fight the scroller's positioning.\n\n::component-preview\n---\nname: MessageScrollerAnimationDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Jumping to Messages\n\nSearch results, permalinks, outline items, and toolbar buttons often need to drive the transcript from outside the message list. Use `useMessageScroller` for those controls — the composables read from `MessageScrollerProvider`, so they work in any component inside the provider.\n\n```vue\n\u003Cscript setup lang=\"ts\">\nimport { useMessageScroller } from '@\u002Fcomponents\u002Fui\u002Fmessage-scroller'\n\nconst { scrollToMessage, scrollToEnd, scrollToStart } = useMessageScroller()\n\u003C\u002Fscript>\n```\n\n::component-preview\n---\nname: MessageScrollerCommandsDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Tracking the Reader's Position\n\nUse `useMessageScrollerVisibility` to track the reader's position — a table-of-contents or jump menu that highlights the current anchored turn. `currentAnchorId` answers \"where am I\" and stays set after that anchor scrolls above the viewport; `visibleMessageIds` answers \"what is on screen\", in document order.\n\n::component-preview\n---\nname: MessageScrollerVisibilityDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n### Reading Scroll State\n\nUse `useMessageScrollerScrollable` when you need scroll state in JavaScript, such as a status indicator or a custom \"jump to latest\" control. It reports which edges the viewport can still scroll toward.\n\n::component-preview\n---\nname: MessageScrollerScrollableDemo\nclass: \"style-luma rounded-[34px] sm:rounded-4xl\"\npreviewClass: h-auto theme-green bg-surface dark:bg-background p-4 min-[480px]:p-8 min-[560px]:p-10 sm:px-10 sm:py-16\n---\n::\n\n## API Reference\n\n### MessageScrollerProvider\n\nOwns the scroll state and behavior. Provide it via `provide`\u002F`inject` and expose the scroll composables to descendants.\n\n| Prop                     | Type                                  | Default        | Description                                                            |\n| ------------------------ | ------------------------------------- | -------------- | -------------------------------------------------------------------- |\n| `autoScroll`             | `boolean`                             | `false`        | Follow the live edge while the reader is pinned to the bottom.        |\n| `defaultScrollPosition`  | `'start' \\| 'end' \\| 'last-anchor'`   | `'end'`        | Opening position for the transcript.                                  |\n| `scrollEdgeThreshold`    | `number`                              | `8`            | Distance in px from an edge before it is considered scrollable.       |\n| `scrollPreviousItemPeek` | `number`                              | `64`           | Amount in px of the previous turn kept visible when anchoring.        |\n| `scrollMargin`           | `number`                              | `0`            | Extra offset in px applied when scrolling to an element.              |\n\n### MessageScrollerViewport\n\n| Prop                       | Type      | Default | Description                                             |\n| -------------------------- | --------- | ------- | ------------------------------------------------------- |\n| `preserveScrollOnPrepend`  | `boolean` | `true`  | Keep the current view when messages are added above.    |\n\nRendered as a `role=\"region\"`, `aria-label=\"Messages\"`, focusable (`tabindex=\"0\"`) native scroll container.\n\n### MessageScrollerItem\n\n| Prop           | Type      | Default | Description                                      |\n| -------------- | --------- | ------- | ------------------------------------------------ |\n| `messageId`    | `string`  | —       | Stable id used for anchoring, visibility, jumps. |\n| `scrollAnchor` | `boolean` | `false` | Marks this row as the start of a turn.           |\n\n### MessageScrollerButton\n\n| Prop        | Type                  | Default   | Description                              |\n| ----------- | --------------------- | --------- | ---------------------------------------- |\n| `direction` | `'start' \\| 'end'`    | `'end'`   | Direction the button scrolls toward.     |\n| `behavior`  | `ScrollBehavior`      | `'smooth'`| Scroll behavior for the jump.            |\n| `variant`   | `ButtonVariants`      | `'secondary'` | Button variant.                      |\n| `size`      | `ButtonVariants`      | `'icon-sm'`   | Button size.                         |\n\nExposes `data-active` for styling and becomes `inert` with `tabindex=\"-1\"` when there is nothing to scroll toward.\n\n### Composables\n\n#### useMessageScroller()\n\n```ts\nconst { scrollToMessage, scrollToEnd, scrollToStart } = useMessageScroller()\n```\n\n- `scrollToMessage(id, options?)` — scroll to the item with the matching `messageId`. Returns `true` if handled (queued if the item is not mounted yet), `false` if the id is missing after rows have mounted.\n- `scrollToEnd(options?)` \u002F `scrollToStart(options?)` — scroll to the live edge or the top.\n\n#### useMessageScrollerVisibility()\n\n```ts\nconst visibility = useMessageScrollerVisibility()\n\u002F\u002F visibility.value.currentAnchorId, visibility.value.visibleMessageIds\n```\n\nTracking only runs while something subscribes, and rows need a `messageId` to participate.\n\n#### useMessageScrollerScrollable()\n\n```ts\nconst scrollable = useMessageScrollerScrollable()\n\u002F\u002F scrollable.value.start, scrollable.value.end\n```\n\nReports which edges the viewport can still scroll toward. For styling the scroller itself, prefer the `data-scrollable` attribute.\n",{"title":169,"description":1259},"SFUxuUgYSocvg5nPugQtYyV3G2iBF-ixwavFD03oAl8",[1267,1268],{"title":165,"path":166,"stem":167,"children":-1},{"title":173,"path":174,"stem":175,"children":-1},1788943235761]