Installation

No MCP yet? Connect your agent.

Usage

Portal Setup Required. A PortalHost must be added at the root of your app to support portal rendering on native platforms. Without it, components that rely on portals, like this one, will not render correctly.

import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
 <Select>
  <SelectTrigger className='w-[180px]'>
    <SelectValue placeholder='Select a fruit' />
  </SelectTrigger>
  <SelectContent insets={contentInsets} className='w-[180px]'>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      <SelectItem label='Apple' value='apple'>
        Apple
      </SelectItem>
      <SelectItem label='Banana' value='banana'>
        Banana
      </SelectItem>
      <SelectItem label='Blueberry' value='blueberry'>
        Blueberry
      </SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>

Scrollable select

React Native Gesture Handler required. Install react-native-gesture-handler in your project and add the following NativeSelectScrollView component to your @/components/ui/select.tsx file or the file using the scrollable select.

import { ScrollView } from 'react-native-gesture-handler';
/**
 * @platform Native only
 * Returns the children on the web
 */
function NativeSelectScrollView({ className, ...props }: React.ComponentProps<typeof ScrollView>) {
  if (Platform.OS === 'web') {
    return <>{props.children}</>;
  }
  return <ScrollView className={cn('max-h-52', className)} {...props} />;
}
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import type { TriggerRef } from '@rn-primitives/select';
import * as React from 'react';
import { Platform } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const fruits = [
  { label: 'Apple', value: 'apple' },
  { label: 'Banana', value: 'banana' },
  { label: 'Blueberry', value: 'blueberry' },
  { label: 'Grapes', value: 'grapes' },
  { label: 'Pineapple', value: 'pineapple' },
  { label: 'Cherry', value: 'cherry' },
  { label: 'Strawberry', value: 'strawberry' },
  { label: 'Orange', value: 'orange' },
  { label: 'Lemon', value: 'lemon' },
  { label: 'Kiwi', value: 'kiwi' },
  { label: 'Mango', value: 'mango' },
  { label: 'Pomegranate', value: 'pomegranate' },
  { label: 'Watermelon', value: 'watermelon' },
  { label: 'Peach', value: 'peach' },
  { label: 'Pear', value: 'pear' },
  { label: 'Plum', value: 'plum' },
  { label: 'Raspberry', value: 'raspberry' },
  { label: 'Tangerine', value: 'tangerine' },
];

export function ScrollableSelectPreview() {
  const ref = React.useRef<TriggerRef>(null);

  const insets = useSafeAreaInsets();
  const contentInsets = {
    top: insets.top,
    bottom: Platform.select({ ios: insets.bottom, android: insets.bottom + 24 }),
    left: 12,
    right: 12,
  };

  return (
    <Select>
      <SelectTrigger ref={ref} className="w-[180px]">
        <SelectValue placeholder="Select a fruit" />
      </SelectTrigger>
      <SelectContent insets={contentInsets} className="w-[180px]">
        <NativeSelectScrollView>
          <SelectGroup>
            <SelectLabel>Fruits</SelectLabel>
            {fruits.map((fruit) => (
              <SelectItem key={fruit.value} label={fruit.label} value={fruit.value}>
                {fruit.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </NativeSelectScrollView>
      </SelectContent>
    </Select>
  );
}

/**
 * @platform Native only
 * Returns the children on the web
 */
function NativeSelectScrollView({ className, ...props }: React.ComponentProps<typeof ScrollView>) {
  if (Platform.OS === 'web') {
    return <>{props.children}</>;
  }
  return <ScrollView className={cn('max-h-52', className)} {...props} />;
}

Examples

import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import type { TriggerRef } from '@rn-primitives/select';
import * as React from 'react';
import { Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const fruits = [
  { label: 'Apple', value: 'apple' },
  { label: 'Banana', value: 'banana' },
  { label: 'Blueberry', value: 'blueberry' },
  { label: 'Grapes', value: 'grapes' },
  { label: 'Pineapple', value: 'pineapple' },
];

export function SelectPreview() {
  const ref = React.useRef<TriggerRef>(null);
  const insets = useSafeAreaInsets();
  const contentInsets = {
    top: insets.top,
    bottom: Platform.select({ ios: insets.bottom, android: insets.bottom + 24 }),
    left: 12,
    right: 12,
  };

  return (
    <Select>
      <SelectTrigger ref={ref} className="w-[180px]">
        <SelectValue placeholder="Select a fruit" />
      </SelectTrigger>
      <SelectContent insets={contentInsets} className="w-[180px]">
        <SelectGroup>
          <SelectLabel>Fruits</SelectLabel>
          {fruits.map((fruit) => (
            <SelectItem key={fruit.value} label={fruit.label} value={fruit.value}>
              {fruit.label}
            </SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
  );
}

Agent guide

Add the cn helper and the theme color variables the files useWhen: The project has no components.json file.From the collection

The files import cn from @/lib/utils:

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Class names such as bg-background, text-muted-foreground, border-border, bg-primary, and rounded-lg resolve through these theme variables. Define them in the global CSS and map each color in the Tailwind theme as hsl(var(--name)), with --radius mapped to borderRadius.lg (md is calc(var(--radius) - 2px), sm is calc(var(--radius) - 4px)).

Light:

VariableValue
--backgroundhsl(0 0% 100%)
--foregroundhsl(0 0% 3.9%)
--cardhsl(0 0% 100%)
--card-foregroundhsl(0 0% 3.9%)
--popoverhsl(0 0% 100%)
--popover-foregroundhsl(0 0% 3.9%)
--primaryhsl(0 0% 9%)
--primary-foregroundhsl(0 0% 98%)
--secondaryhsl(0 0% 96.1%)
--secondary-foregroundhsl(0 0% 9%)
--mutedhsl(0 0% 96.1%)
--muted-foregroundhsl(0 0% 45.1%)
--accenthsl(0 0% 96.1%)
--accent-foregroundhsl(0 0% 9%)
--destructivehsl(0 84.2% 60.2%)
--borderhsl(0 0% 89.8%)
--inputhsl(0 0% 89.8%)
--ringhsl(0 0% 63%)
--radius0.625rem

Dark:

VariableValue
--backgroundhsl(0 0% 3.9%)
--foregroundhsl(0 0% 98%)
--cardhsl(0 0% 3.9%)
--card-foregroundhsl(0 0% 98%)
--popoverhsl(0 0% 3.9%)
--popover-foregroundhsl(0 0% 98%)
--primaryhsl(0 0% 98%)
--primary-foregroundhsl(0 0% 9%)
--secondaryhsl(0 0% 14.9%)
--secondary-foregroundhsl(0 0% 98%)
--mutedhsl(0 0% 14.9%)
--muted-foregroundhsl(0 0% 63.9%)
--accenthsl(0 0% 14.9%)
--accent-foregroundhsl(0 0% 98%)
--destructivehsl(0 70.9% 59.4%)
--borderhsl(0 0% 14.9%)
--inputhsl(0 0% 14.9%)
--ringhsl(300 0% 45%)
--radius0.625rem

Nativewind projects: the files assume 1rem is 16px, so pass inlineRem: 16 to Nativewind in metro.config.js:

module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 });

Related items

Requires