Island
npx @vyui/cli add islandOverview
VyIsland is a Linear-inspired container for compact navigation and actions. It can float at a viewport edge or participate in normal layout, and it coordinates three independent state axes for child VyIslandButton components: selected value, active row mode, and expanded open state.
Usage
Compose an island from VyIslandButton children. Set layer="inline" when the island should remain in document flow.
<script setup lang="ts">
import { ref } from 'vue'
import { VyIsland, VyIslandButton } from '@vyui/kit'
const section = ref<string | number | null>('inbox')
</script>
<template>
<VyIsland v-model:value="section" layer="inline">
<VyIslandButton value="inbox" icon="i-lucide-inbox" />
<VyIslandButton value="activity" icon="i-lucide-bell" />
<VyIslandButton value="profile" icon="i-lucide-user" />
</VyIsland>
</template>
Buttons with a value update the island model and automatically receive active styling when their value matches.
Row modes
Use mode to replace the normal row with a named slot. Mode names are free-form strings. A button with the same mode switches the parent, and a button with reset returns it to default.
<script setup lang="ts">
import { ref } from 'vue'
import { VyIsland, VyIslandButton } from '@vyui/kit'
const mode = ref('default')
</script>
<template>
<VyIsland v-model:mode="mode" layer="inline">
<VyIslandButton icon="i-lucide-inbox" />
<VyIslandButton mode="search" icon="i-lucide-search" />
<template #search>
<VyIslandButton
reset
icon="i-lucide-search"
label="Search messages…"
:style="{ flexGrow: 1, justifyContent: 'flex-start' }"
/>
<VyIslandButton reset icon="i-lucide-x" />
</template>
</VyIsland>
</template>
If the current mode has no matching named slot, the island safely falls back to the default slot.
Expandable panel
Add an expanded slot and use an expand button to toggle it. For a bottom island, the panel appears above the row; position="top" places it below.
<script setup lang="ts">
import { ref } from 'vue'
import { VyIsland, VyIslandButton } from '@vyui/kit'
const open = ref(false)
</script>
<template>
<VyIsland v-model:open="open" layer="inline">
<VyIslandButton icon="i-lucide-home" />
<VyIslandButton
expand
:icon="open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-up'"
/>
<template #expanded="{ close }">
<VyIslandButton
icon="i-lucide-settings"
label="Settings"
class="w-full justify-start"
@tap="close"
/>
<VyIslandButton
icon="i-lucide-circle-help"
label="Help"
class="w-full justify-start"
@tap="close"
/>
</template>
</VyIsland>
</template>
expand-style="floating" keeps the panel and row as separate surfaces. Use expand-style="attached" to merge them into one rounded surface while open.
Floating placement
The default layer="overlay" fixes the island 16 pixels from the bottom edge at z-index: 50.
<VyIsland position="top" layer="base" size="sm">
<VyIslandButton icon="i-lucide-arrow-left" accessibility-label="Go back" />
<VyIslandButton label="Project settings" />
</VyIsland>
Use layer="base" when drawers and modals should render above the island. Use layer="inline" for cards, toolbars, custom corner placement, or islands managed by VyIslandGroup.
Features and behavior
value,mode, andopenare independent; one island can use any combination of them.- Each axis supports controlled state through
v-model:*and uncontrolled state through its matchingdefault*prop. - In controlled mode, the component emits an update but continues to render the supplied prop until the parent updates it.
- A single renderable row child activates compact symmetric padding, allowing an icon-only island to appear circular.
- Whitespace, comments, false
v-ifbranches, and fragments are handled when counting row children. - The
expandedslot must exist for the island to enter its rendered open state. Without it,opencan update, but no panel appears and slot props reportopen: false. sizeis provided to descendantVyIslandButtoncomponents. A button's explicitsizetakes precedence.data-stateisopenorclosed, anddata-modecontains the resolved row mode.
State composition
The default and named row slots, plus the expanded slot, all receive the same state helpers.
<VyIsland v-slot="{ value, setValue, toggle }" layer="inline">
<VyIslandButton
label="Inbox"
:active="value === 'inbox'"
@tap="setValue('inbox')"
/>
<VyIslandButton label="More" @tap="toggle" />
<template #expanded="{ setMode, close }">
<VyIslandButton label="Compose" @tap="setMode('compose')" />
<VyIslandButton label="Close" @tap="close" />
</template>
</VyIsland>
Prefer the declarative value, mode, expand, and reset props on VyIslandButton for common cases. The helpers are useful for custom content or compound actions.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | undefined | Controlled expanded state used by v-model:open. |
defaultOpen | boolean | false | Initial expanded state when uncontrolled. |
mode | string | undefined | Controlled row mode used by v-model:mode. |
defaultMode | string | 'default' | Initial row mode when uncontrolled. |
value | string | number | null | undefined | Controlled selected value used by v-model:value. |
defaultValue | string | number | null | null | Initial selected value when uncontrolled. |
position | 'top' | 'bottom' | 'bottom' | Floating viewport edge and panel growth direction. Ignored by inline layers for placement. |
layer | 'overlay' | 'base' | 'inline' | 'overlay' | Floating and stacking strategy. |
expandStyle | 'floating' | 'attached' | 'floating' | Visual relationship between the row and an open panel. |
size | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Island spacing and inherited button size. |
as | string | 'view' | Element or component used for the root. |
class | any | undefined | Classes merged onto the root slot. |
ui | Partial<Record<IslandSlot, any>> | undefined | Per-instance theme slot overrides. |
Emits
| Event | Payload | Description |
|---|---|---|
update:open | boolean | The expanded state changed. |
update:mode | string | The active row mode changed. |
update:value | string | number | null | The selected value changed. |
Slots
Every slot receives { open, mode, value, setOpen, toggle, close, setMode, resetMode, setValue }.
| Slot | Description |
|---|---|
default | Row rendered when mode is 'default' or the selected named slot does not exist. |
expanded | Panel rendered while the resolved open state is true. Its presence enables the rendered open state. |
[mode] | Any named slot whose name matches the current free-form mode value. |
setOpen(boolean), setMode(string), and setValue(value) write local state and emit their corresponding update event. toggle, close, and resetMode are convenience helpers.
Styling and theming
Override globally through appConfig.ui.island or locally with ui.
| UI slot | Purpose |
|---|---|
root | Outer layout, panel-to-row gap, width limit, and attached-surface chrome. |
row | Main pill surface and horizontal child layout. |
panel | Expandable surface, vertical layout, scrolling, and height limit. |
The theme combines position, size, expandStyle, rendered open state, and automatic solo detection.
| Size | Button target | Typical use |
|---|---|---|
sm | 40px | Compact toolbars and secondary islands. |
md | 44px | Default controls and minimum comfortable target. |
lg | 56px | Prominent mobile docks. |
xl | 64px | Oversized or presentation-focused docks. |
Both surfaces use a translucent white background, backdrop blur, subtle border, and shadow. The default theme is light-mode-oriented. In attached mode, open-state chrome moves to root, the gap collapses, and row and panel become transparent sections.
Accessibility
VyIsland is a visual and state-coordination container; it does not assign a navigation, toolbar, or group role. Choose the semantic context around it based on the content. Its built-in VyIslandButton children use native Lynx button semantics and announce disabled state.
Give every icon-only button an accessibility-label. When a value button represents navigation, expose the destination in visible or accessible text and communicate the current destination in surrounding page content; active styling alone is not a complete selection announcement. Keep custom mode and panel content in a predictable focus order, and test expanding or morphing layouts with VoiceOver and TalkBack.
Platform notes
- Floating placement is applied with an inline
position: fixedstyle because Lynx does not reliably apply the Tailwindfixedutility. - Floating islands span the viewport from
left: 0toright: 0and center the pill. Override inlinestylefor custom offsets or corner placement. overlayusesz-index: 50;baseusesz-index: 10;inlineapplies no positioning style.- The panel grows away from the selected edge and is capped at
calc(100dvh - 4rem)with vertical scrolling. - The component uses Lynx
viewandtextconventions rather than assuming DOM-only elements.
Related components
Island Buttonfor declarative actions, selection, modes, and expansion.Island Groupfor positioning multiple companion islands together.Dropdown Menufor an anchored menu with menu semantics.Tabsfor persistent peer panels with explicit tab behavior.