Repository: linode/manager
Pull request #11985: upcoming: [M3-9714] - Update Linode Create UI for Linode Interfaces
Author: login: bnussman-akamai
Labels: name: Approved
description: Multiple approvals and ready to merge!
name: Linode Interfaces
Created: 2025-04-07T21:31:35Z | Merged: 2025-04-10T23:41:01Z

## Description 📝

Updates the Linode Create flow Networking UI to match the finalized UX

## Changes  🔄

- Only a single interface can be configured now 1️⃣ 
- New radio selection card thing ✨ 
  - Kind of a new pattern that UX wants to try here
- Moved the position of some fields ↕️ 
- Made "Network Interface Type" an expandable section ⬇️ 

> [!note]
> This should be the final UI but it will likely need updated copy at some point soon

## Preview 📷

| Before  | After   |
| ------- | ------- |
| ![Screenshot 2025-04-07 at 5 26 33 PM](https://github.com/user-attachments/assets/567149ee-ef1d-45ba-89c2-2cada01f47f8) |  ![Screenshot 2025-04-07 at 5 26 12 PM](https://github.com/user-attachments/assets/8bd83443-0b14-42f4-a788-7fdeaa4f2cda) |

## How to test 🧪

### Prerequisites

- Have `new-interfaces-beta` customer tag
- Turn on the Linode Interfaces feature flag using Cloud's local dev tools

### Verification steps

- Test the Linode Create flow networking section with various different account settings and network interface configurations 🧪  
- Check that this PR's UI matches what UX provided in the mockups

<details>
<summary> Author Checklists </summary>

## As an Author, to speed up the review process, I considered 🤔

👀 Doing a self review
❔ Our [contribution guidelines](https://github.com/linode/manager/blob/develop/docs/CONTRIBUTING.md)
🤏 Splitting feature into small PRs
➕ Adding a [changeset](https://github.com/linode/manager/blob/develop/docs/CONTRIBUTING.md#writing-a-changeset)
🧪 Providing/improving test coverage
 🔐 Removing all sensitive information from the code and PR description
🚩 Using a feature flag to protect the release
👣 Providing comprehensive reproduction steps
📑 Providing or updating our documentation
🕛 Scheduling a pair reviewing session
📱 Providing mobile support
♿  Providing accessibility support

<br/>

- [ ] I have read and considered all applicable items listed above.

## As an Author, before moving this PR from Draft to Open, I confirmed ✅

- [ ] All unit tests are passing
- [ ] TypeScript compilation succeeded without errors
- [ ] Code passes all linting rules

</details>


Diff:
diff --git a/packages/manager/.changeset/pr-11985-upcoming-features-1744141507075.md b/packages/manager/.changeset/pr-11985-upcoming-features-1744141507075.md
new file mode 100644
index 00000000000..1f30d2ae328
--- /dev/null
+++ b/packages/manager/.changeset/pr-11985-upcoming-features-1744141507075.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Upcoming Features
+---
+
+Update UI of the Networking section on the Linode Create flow ([#11985](https://github.com/linode/manager/pull/11985))
diff --git a/packages/manager/src/components/SelectionCard/CardBase.styles.ts b/packages/manager/src/components/SelectionCard/CardBase.styles.ts
index 178da62a939..f202886dd45 100644
--- a/packages/manager/src/components/SelectionCard/CardBase.styles.ts
+++ b/packages/manager/src/components/SelectionCard/CardBase.styles.ts
@@ -49,19 +49,18 @@ export const CardBaseGrid = styled(Grid, {
 
 export const CardBaseIcon = styled(Grid, {
   label: 'CardBaseIcon',
-})(({ theme }) => ({
+})({
   '& img': {
     maxHeight: 32,
     maxWidth: 32,
   },
   '& svg, & span': {
-    color: theme.tokens.color.Neutrals[50],
     fontSize: 32,
   },
   alignItems: 'flex-end',
   display: 'flex',
   justifyContent: 'flex-end',
-}));
+});
 
 export const CardBaseHeadings = styled(Grid, {
   label: 'CardBaseHeadings',
diff --git a/packages/manager/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx b/packages/manager/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx
index 5d1d983451e..c0165d58447 100644
--- a/packages/manager/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx
+++ b/packages/manager/src/components/ShowMoreExpansion/ShowMoreExpansion.tsx
@@ -4,6 +4,7 @@ import Collapse from '@mui/material/Collapse';
 import * as React from 'react';
 import { makeStyles } from 'tss-react/mui';
 
+import type { ButtonProps } from '@linode/ui';
 import type { Theme } from '@mui/material/styles';
 
 const useStyles = makeStyles<void, 'caret'>()(
@@ -39,6 +40,10 @@ const useStyles = makeStyles<void, 'caret'>()(
 );
 
 export interface ShowMoreExpansionProps {
+  /**
+   * Optional props that will be passed to the underlying button
+   */
+  ButtonProps?: ButtonProps;
   /**
    * The content that will be shown when the component is expanded.
    */
@@ -54,7 +59,7 @@ export interface ShowMoreExpansionProps {
 }
 
 export const ShowMoreExpansion = (props: ShowMoreExpansionProps) => {
-  const { children, defaultExpanded, name } = props;
+  const { ButtonProps, children, defaultExpanded, name } = props;
 
   const { classes } = useStyles();
 
@@ -74,6 +79,7 @@ export const ShowMoreExpansion = (props: ShowMoreExpansionProps) => {
         data-qa-show-more-toggle
         onClick={handleNameClick}
         role="button"
+        {...ButtonProps}
       >
         {open ? (
           <KeyboardArrowRight className={classes.caret + ' rotate'} />
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx
index cc728ef01d6..88741a527f0 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx
@@ -1,3 +1,4 @@
+import userEvent from '@testing-library/user-event';
 import React from 'react';
 
 import { accountSettingsFactory } from 'src/factories';
@@ -18,13 +19,23 @@ describe('InterfaceGeneration', () => {
       })
     );
 
-    const { findByLabelText, getAllByRole } = renderWithThemeAndHookFormContext(
-      {
-        component: <InterfaceGeneration />,
-      }
-    );
+    const {
+      findByText,
+      getAllByRole,
+      getByText,
+    } = renderWithThemeAndHookFormContext({
+      component: <InterfaceGeneration />,
+    });
+
+    const button = getByText('Network Interface Type');
+
+    // Expand the "Show More"
+    await userEvent.click(button);
 
-    await findByLabelText(
+    // Hover to check for the tooltip
+    await userEvent.hover(getByText('Network Interface Type'));
+
+    await findByText(
       'You account administrator has enforced that all new Linodes are created with Linode interfaces.'
     );
 
@@ -44,17 +55,31 @@ describe('InterfaceGeneration', () => {
       })
     );
 
-    const { findByLabelText, getAllByRole } = renderWithThemeAndHookFormContext(
-      {
-        component: <InterfaceGeneration />,
-      }
-    );
+    const {
+      findByText,
+      getAllByRole,
+      getByText,
+    } = renderWithThemeAndHookFormContext({
+      component: <InterfaceGeneration />,
+    });
 
-    await findByLabelText(
+    const button = getByText('Network Interface Type');
+
+    // Expand the "Show More"
+    await userEvent.click(button);
+
+    // Hover to check for the tooltip
+    await userEvent.hover(getByText('Network Interface Type'));
+
+    await findByText(
       'You account administrator has enforced that all new Linodes are created with legacy configuration interfaces.'
     );
 
-    for (const radio of getAllByRole('radio')) {
+    const radios = getAllByRole('radio');
+
+    expect(radios).toHaveLength(2);
+
+    for (const radio of radios) {
       expect(radio).toBeDisabled();
     }
   });
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
index 63f8308f635..7c449d4e9e1 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
@@ -1,15 +1,15 @@
 import { useAccountSettings } from '@linode/queries';
 import {
+  Box,
   FormControl,
   FormControlLabel,
   Radio,
   RadioGroup,
-  TooltipIcon,
 } from '@linode/ui';
 import React from 'react';
 import { useController } from 'react-hook-form';
 
-import { FormLabel } from 'src/components/FormLabel';
+import { ShowMoreExpansion } from 'src/components/ShowMoreExpansion';
 
 import type { LinodeCreateFormValues } from '../utilities';
 import type { LinodeInterfaceAccountSetting } from '@linode/api-v4';
@@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
+        ButtonProps={{
+          TooltipProps: {
+            placement: 'right',
+          },
+          alwaysShowTooltip: disabled,
+          tooltipText: disabledReason,
+        }}
+        defaultExpanded={!disabled}
+        name="Network Interface Type"
       >
-        <FormControlLabel
-          control={<Radio />}
-          label="Configuration Profile Interfaces (Legacy)"
-          value="legacy_config"
-        />
-        <FormControlLabel
-          control={<Radio />}
-          label="Linode Interfaces (New)"
-          value="linode"
-        />
-      </RadioGroup>
-    </FormControl>
+        <FormControl disabled={disabled} sx={{ my: '0px !important', mx: 0.5 }}>
+          <RadioGroup
+            aria-labelledby="interface-generation"
+            onChange={field.onChange}
+            sx={{ my: '0px !important' }}
+            value={field.value ?? 'legacy_config'}
+          >
+            <FormControlLabel
+              control={<Radio />}
+              label="Configuration Profile Interfaces (Legacy)"
+              value="legacy_config"
+            />
+            <FormControlLabel
+              control={<Radio />}
+              label="Linode Interfaces (New)"
+              value="linode"
+            />
+          </RadioGroup>
+        </FormControl>
+      </ShowMoreExpansion>
+    </Box>
   );
 };
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
index 9cec47c7906..b21678a2bc4 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
@@ -1,9 +1,11 @@
 import { useFirewallSettingsQuery } from '@linode/queries';
-import { FormControl, FormControlLabel, Radio, RadioGroup } from '@linode/ui';
+import { InputLabel, Radio, RadioGroup } from '@linode/ui';
+import { Grid2 } from '@mui/material';
 import React from 'react';
 import { useController, useFormContext } from 'react-hook-form';
 
-import { FormLabel } from 'src/components/FormLabel';
+import { FormGroup } from 'src/components/FormGroup';
+import { SelectionCard } from 'src/components/SelectionCard/SelectionCard';
 
 import { getDefaultFirewallForInterfacePurpose } from './utilities';
 
@@ -14,11 +16,26 @@ interface Props {
   index: number;
 }
 
+const interfaceTypes = [
+  {
+    label: 'Public Internet',
+    purpose: 'public',
+  },
+  {
+    label: 'VPC',
+    purpose: 'vpc',
+  },
+  {
+    label: 'VLAN',
+    purpose: 'vlan',
+  },
+] as const;
+
 export const InterfaceType = ({ index }: Props) => {
   const {
     control,
-    setValue,
     getFieldState,
+    setValue,
   } = useFormContext<LinodeCreateFormValues>();
 
   const { data: firewallSettings } = useFirewallSettingsQuery();
@@ -28,44 +45,54 @@ export const InterfaceType = ({ index }: Props) => {
     name: `linodeInterfaces.${index}.purpose`,
   });
 
-  return (
-    <FormControl>
-      <FormLabel id="network-interface" sx={{ mb: 0 }}>
-        Network Connection
-      </FormLabel>
-      <RadioGroup
-        onChange={(e, value) => {
-          // Change the interface purpose (Public, VPC, VLAN)
-          field.onChange(value);
+  const onChange = (value: InterfacePurpose) => {
+    // Change the interface purpose (Public, VPC, VLAN)
+    field.onChange(value);
+
+    const defaultFirewall = getDefaultFirewallForInterfacePurpose(
+      value,
+      firewallSettings
+    );
 
-          const defaultFirewall = getDefaultFirewallForInterfacePurpose(
-            value as InterfacePurpose,
-            firewallSettings
-          );
+    // Set the Firewall based on defaults if:
+    // - there is a default firewall for this interface type
+    // - the user has not touched the Firewall field
+    if (
+      defaultFirewall &&
+      !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
+    ) {
+      setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
+    }
+  };
 
-          // Set the Firewall based on defaults if:
-          // - there is a default firewall for this interface type
-          // - the user has not touched the Firewall field
-          if (
-            defaultFirewall &&
-            !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
-          ) {
-            setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
-          }
-        }}
-        aria-labelledby="network-interface"
-        row
-        sx={{ mb: '0px !important' }}
-        value={field.value}
+  return (
+    <FormGroup sx={{ display: 'block' }}>
+      <InputLabel id="network-connection-label">Network Connection</InputLabel>
+      <RadioGroup
+        aria-labelledby="network-connection-label"
+        sx={{ display: 'block', marginBottom: '0px !important' }}
       >
-        <FormControlLabel
-          control={<Radio />}
-          label="Public Internet"
-          value="public"
-        />
-        <FormControlLabel control={<Radio />} label="VPC" value="vpc" />
-        <FormControlLabel control={<Radio />} label="VLAN" value="vlan" />
+        <Grid2 container spacing={1}>
+          {interfaceTypes.map((interfaceType) => (
+            <SelectionCard
+              gridSize={{
+                md: 3,
+                sm: 12,
+                xs: 12,
+              }}
+              renderIcon={() => (
+                <Radio checked={field.value === interfaceType.purpose} />
+              )}
+              checked={field.value === interfaceType.purpose}
+              heading={interfaceType.label}
+              key={interfaceType.purpose}
+              onClick={() => onChange(interfaceType.purpose)}
+              subheadings={[]}
+              sxCardBaseIcon={{ svg: { fontSize: '24px !important' } }}
+            />
+          ))}
+        </Grid2>
       </RadioGroup>
-    </FormControl>
+    </FormGroup>
   );
 };
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/LinodeInterface.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/LinodeInterface.tsx
index 88ddc67987d..3bfab56a821 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/LinodeInterface.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/LinodeInterface.tsx
@@ -1,8 +1,9 @@
-import { Button, Notice, Stack, Typography } from '@linode/ui';
+import { Notice, Stack } from '@linode/ui';
 import React from 'react';
 import { useFormContext, useWatch } from 'react-hook-form';
 
 import { InterfaceFirewall } from './InterfaceFirewall';
+import { InterfaceGeneration } from './InterfaceGeneration';
 import { InterfaceType } from './InterfaceType';
 import { VLAN } from './VLAN';
 import { VPC } from './VPC';
@@ -11,10 +12,9 @@ import type { LinodeCreateFormValues } from '../utilities';
 
 interface Props {
   index: number;
-  onRemove: () => void;
 }
 
-export const LinodeInterface = ({ index, onRemove }: Props) => {
+export const LinodeInterface = ({ index }: Props) => {
   const {
     control,
     formState: { errors },
@@ -32,15 +32,6 @@ export const LinodeInterface = ({ index, onRemove }: Props) => {
 
   return (
     <Stack spacing={2}>
-      <Stack
-        alignItems="center"
-        direction="row"
-        justifyContent="space-between"
-        spacing={2}
-      >
-        <Typography variant="h3">Interface eth{index}</Typography>
-        {index !== 0 && <Button onClick={onRemove}>Remove Interface</Button>}
-      </Stack>
       {errors.linodeInterfaces?.[index]?.message && (
         <Notice
           text={errors.linodeInterfaces?.[index]?.message}
@@ -53,7 +44,10 @@ export const LinodeInterface = ({ index, onRemove }: Props) => {
           variant="error"
         />
       )}
-      <InterfaceType index={index} />
+      <Stack spacing={1}>
+        <InterfaceType index={index} />
+        <InterfaceGeneration />
+      </Stack>
       {interfaceType === 'vlan' && <VLAN index={index} />}
       {interfaceType === 'vpc' && <VPC index={index} />}
       {interfaceGeneration === 'linode' && <InterfaceFirewall index={index} />}
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/Networking.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/Networking.tsx
index bd1ebda2ab0..b2820e15a38 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/Networking.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/Networking.tsx
@@ -1,20 +1,9 @@
-import { useFirewallSettingsQuery } from '@linode/queries';
-import {
-  Button,
-  Divider,
-  Notice,
-  Paper,
-  PlusSignIcon,
-  Stack,
-  Typography,
-} from '@linode/ui';
+import { Notice, Paper, Stack, Typography } from '@linode/ui';
 import React from 'react';
 import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
 
 import { Firewall } from './Firewall';
-import { InterfaceGeneration } from './InterfaceGeneration';
 import { LinodeInterface } from './LinodeInterface';
-import { getDefaultInterfacePayload } from './utilities';
 
 import type { LinodeCreateFormValues } from '../utilities';
 
@@ -24,9 +13,10 @@ export const Networking = () => {
     formState: { errors },
   } = useFormContext<LinodeCreateFormValues>();
 
-  const { data: firewallSettings } = useFirewallSettingsQuery();
-
-  const { append, fields, remove } = useFieldArray({
+  // Our state is an array of interfaces in case we ever
+  // allow the user to configure multiple interfaces on
+  // the Linode Create flow.
+  const { fields } = useFieldArray({
     control,
     name: 'linodeInterfaces',
   });
@@ -38,33 +28,13 @@ export const Networking = () => {
 
   return (
     <Paper>
-      <Stack divider={<Divider />} spacing={2}>
-        <Stack
-          alignItems="center"
-          direction="row"
-          justifyContent="space-between"
-        >
-          <Typography variant="h2">Networking</Typography>
-          <Button
-            onClick={() => {
-              append(getDefaultInterfacePayload('public', firewallSettings));
-            }}
-            buttonType="outlined"
-            endIcon={<PlusSignIcon height="12px" width="12px" />}
-          >
-            Add Another Interface
-          </Button>
-        </Stack>
+      <Stack spacing={2}>
+        <Typography variant="h2">Networking</Typography>
         {errors.linodeInterfaces?.message && (
           <Notice text={errors.linodeInterfaces.message} variant="error" />
         )}
-        <InterfaceGeneration />
         {fields.map((field, index) => (
-          <LinodeInterface
-            index={index}
-            key={field.id}
-            onRemove={() => remove(index)}
-          />
+          <LinodeInterface index={index} key={field.id} />
         ))}
         {interfaceGeneration !== 'linode' && <Firewall />}
       </Stack>
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx
index 2e2ed7a477d..1414eec19b9 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx
@@ -20,6 +20,7 @@ import {
 } from 'src/features/VPCs/constants';
 import { VPCCreateDrawer } from 'src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer';
 
+import { VPCAvailability } from './VPCAvailability';
 import { VPCRanges } from './VPCRanges';
 
 import type { LinodeCreateFormValues } from '../utilities';
@@ -47,7 +48,11 @@ export const VPC = ({ index }: Props) => {
   const regionSupportsVPCs =
     selectedRegion?.capabilities.includes('VPCs') ?? false;
 
-  const { data: vpcs, error, isLoading } = useAllVPCsQuery({
+  const {
+    data: vpcs,
+    error,
+    isLoading,
+  } = useAllVPCsQuery({
     enabled: regionSupportsVPCs,
     filter: { region: regionId },
   });
@@ -63,12 +68,7 @@ export const VPC = ({ index }: Props) => {
             variant="warning"
           />
         )}
-        {selectedRegion && !regionSupportsVPCs && (
-          <Notice
-            text="VPC is not available in the selected region."
-            variant="warning"
-          />
-        )}
+        {selectedRegion && !regionSupportsVPCs && <VPCAvailability />}
         <Controller
           render={({ field, fieldState }) => (
             <Autocomplete
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCAvailability.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCAvailability.tsx
new file mode 100644
index 00000000000..b05a95c7f91
--- /dev/null
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCAvailability.tsx
@@ -0,0 +1,38 @@
+import { useRegionsQuery } from '@linode/queries';
+import { List, ListItem, Notice, Typography } from '@linode/ui';
+import React from 'react';
+
+import { TextTooltip } from 'src/components/TextTooltip';
+
+export const VPCAvailability = () => {
+  const { data: regions } = useRegionsQuery();
+
+  const regionsThatSupportVPC = regions?.filter((r) =>
+    r.capabilities.includes('VPCs')
+  );
+
+  return (
+    <Notice variant="warning">
+      <Typography
+        sx={(theme) => ({ font: theme.font.bold, fontSize: 'inherit' })}
+      >
+        VPCs are currently available in{' '}
+        <TextTooltip
+          displayText="select regions"
+          minWidth={400}
+          sxTypography={{ fontSize: 'inherit' }}
+          tooltipText={
+            <List sx={{ columns: '2 auto' }}>
+              {regionsThatSupportVPC?.map((region) => (
+                <ListItem disablePadding key={region.id} sx={{ py: 0.5 }}>
+                  {region.label} ({region.id})
+                </ListItem>
+              ))}
+            </List>
+          }
+        />
+        .
+      </Typography>
+    </Notice>
+  );
+};
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCRanges.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCRanges.tsx
index f73944a79d5..1a4c18eba19 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCRanges.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCRanges.tsx
@@ -1,5 +1,6 @@
 import {
   IconButton,
+  Notice,
   Stack,
   TextField,
   TooltipIcon,
@@ -20,7 +21,10 @@ interface Props {
 }
 
 export const VPCRanges = ({ disabled, interfaceIndex }: Props) => {
-  const { control } = useFormContext<LinodeCreateFormValues>();
+  const {
+    control,
+    formState: { errors },
+  } = useFormContext<LinodeCreateFormValues>();
 
   const { append, fields, remove } = useFieldArray({
     control,
@@ -30,6 +34,15 @@ export const VPCRanges = ({ disabled, interfaceIndex }: Props) => {
   return (
     <Stack>
       <Stack spacing={1}>
+        {errors?.linodeInterfaces?.[interfaceIndex]?.vpc?.ipv4?.ranges
+          ?.message && (
+          <Notice variant="error">
+            {
+              errors?.linodeInterfaces?.[interfaceIndex]?.vpc?.ipv4?.ranges
+                ?.message
+            }
+          </Notice>
+        )}
         {fields.map((field, index) => (
           <Stack
             alignItems="flex-start"
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts
index 22335647e03..8f333f2c462 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts
@@ -83,6 +83,21 @@ describe('getLinodeInterfaceErrorsFromLegacyInterfaceErrors', () => {
       },
     ]);
   });
+
+  it('handles a complex VPC IP general range error', () => {
+    const error: APIError[] = [
+      { field: 'interfaces[1].ip_ranges', reason: 'Range is invalid.' },
+    ];
+
+    expect(
+      transformLegacyInterfaceErrorsToLinodeInterfaceErrors(error)
+    ).toStrictEqual([
+      {
+        field: 'linodeInterfaces[1].vpc.ipv4.ranges',
+        reason: 'Range is invalid.',
+      },
+    ]);
+  });
 });
 
 describe('getDefaultFirewallForInterfacePurpose', () => {
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts
index 9e5b61e176c..5b6b1773f77 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts
+++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts
@@ -91,6 +91,7 @@ export const getLegacyInterfaceFromLinodeInterface = (
 };
 
 const legacyFieldToNewFieldMap = {
+  '].ip_ranges': '].vpc.ipv4.ranges',
   '].ipv4.nat_1_1': '].vpc.ipv4.addresses.0.nat_1_1_address',
   '].ipv4.vpc': '].vpc.ipv4.addresses.0.address',
   '].label': '].vlan.vlan_lanel',
@@ -109,13 +110,7 @@ export const transformLegacyInterfaceErrorsToLinodeInterfaceErrors = (
 ) => {
   for (const error of errors) {
     for (const key in legacyFieldToNewFieldMap) {
-      if (error.field && error.field.includes(key)) {
-        error.field = error.field.replace(
-          key,
-          legacyFieldToNewFieldMap[key as keyof typeof legacyFieldToNewFieldMap]
-        );
-      }
-      if (error.field && error.field.includes('ip_ranges')) {
+      if (error.field && error.field.match(/ip_ranges\[(\d+)\]/)) {
         // Handle the more complex case where:
         // `interfaces[0].ip_ranges[1]` should map to `linodeInterfaces.0.vpc.ipv4.ranges.1.range`
         error.field = error.field.replace(
@@ -123,6 +118,12 @@ export const transformLegacyInterfaceErrorsToLinodeInterfaceErrors = (
           'vpc.ipv4.ranges.$1.range'
         );
       }
+      if (error.field && error.field.includes(key)) {
+        error.field = error.field.replace(
+          key,
+          legacyFieldToNewFieldMap[key as keyof typeof legacyFieldToNewFieldMap]
+        );
+      }
       if (error.field && error.field.startsWith('interfaces')) {
         error.field = error.field.replace('interfaces', 'linodeInterfaces');
       }
diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx
index 1602133a68b..d0ac4ddb9b2 100644
--- a/packages/ui/src/components/Button/Button.tsx
+++ b/packages/ui/src/components/Button/Button.tsx
@@ -4,7 +4,7 @@ import { styled } from '@mui/material/styles';
 import * as React from 'react';
 
 import { omittedProps } from '../../utilities';
-import { Tooltip } from '../Tooltip';
+import { Tooltip, TooltipProps } from '../Tooltip';
 
 import type { ButtonProps as _ButtonProps } from '@mui/material/Button';
 import type { SxProps, Theme } from '@mui/material/styles';
@@ -53,6 +53,10 @@ export interface ButtonProps extends _ButtonProps {
   tooltipAnalyticsEvent?: () => void;
   /** Tooltip text */
   tooltipText?: string | JSX.Element;
+  /**
+   * Optional props passed to the tooltip
+   */
+  TooltipProps?: Partial<TooltipProps>;
 }
 
 const StyledButton = styled(_Button, {
@@ -83,6 +87,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
       sxEndIcon,
       tooltipAnalyticsEvent,
       tooltipText,
+      TooltipProps,
       ...rest
     },
     ref
@@ -134,6 +139,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
           id="button-tooltip"
           onClick={handleTooltipAnalytics}
           title={tooltipText}
+          {...TooltipProps}
         >
           {button}
         </Tooltip>


Reviews:
user: login: coliu-akamai
body: Awesome, thanks Banks! 
✅ functionality looks good 

Some thoughts: 
❓ Now that we're only allowing one interface, customers on the new UI but using legacy interfaces have a higher chance of setting themselves up with unrecommended configurations - ie VLAN only (whereas for the old UI, if a customer has only a VLAN interface, we add a public interface in front). 

Wondering if we should try to match the old functionality for legacy config interfaces, or if we instead assume the customer knows what they're doing and will update their configs/interfaces after creation:

![image](https://github.com/user-attachments/assets/afceb009-0922-41b3-8a57-dbc74dc848ef)

payload for new UI, legacy VLAN interface:
![image](https://github.com/user-attachments/assets/c9a1ec48-2ff0-4923-ae42-94349422bc48)

🖌️  Other than that, some minor UX comments below
We should include a changeset too (?)


state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:27:10Z
user: login: abailly-akamai
body: Nice work and concise PR. This feature is really complicated in the way it plays along with old interfaces, so I don't have as much understanding as @coliu-akamai in the way it should behave. That being said I could confirm it is working well (and no regression) with all the combinations I've tried. 

Approving pending some cosmetic improvements suggested by @coliu-akamai (Radio alignment especially). 

Another UX suggestion for later would be to add a list of VPC & VLAN available regions similarly to the plan selection availability UI so the user does not have to try a bunch of regions before getting the right one. 

![Screenshot 2025-04-09 at 10 13 06](https://github.com/user-attachments/assets/cbba9c40-f976-42d0-8dc0-5299d1f2e9c1)


state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T14:24:54Z
user: login: bnussman-akamai
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T22:56:18Z
user: login: bnussman-akamai
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T22:56:45Z

Review comments:
diff_hunk: @@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
+        ButtonProps={{
+          TooltipProps: {
+            placement: 'right',
+          },
+          alwaysShowTooltip: disabled,
+          tooltipText: disabledReason,
+        }}
+        defaultExpanded={!disabled}
+        name="Network Interface Type"
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
user: login: coliu-akamai
body: Should the tooltip for Network Interface type match the tooltip colors in other parts of the form?

| network interface type | other tooltips |
|--------|--------|
|  ![image](https://github.com/user-attachments/assets/a6285942-005e-4736-8159-3802ef6fad55) | ![image](https://github.com/user-attachments/assets/bfc248d2-2553-429e-a80a-af9f19603218) |
created_at: 2025-04-08T14:41:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
+        ButtonProps={{
+          TooltipProps: {
+            placement: 'right',
+          },
+          alwaysShowTooltip: disabled,
+          tooltipText: disabledReason,
+        }}
+        defaultExpanded={!disabled}
+        name="Network Interface Type"
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
user: login: coliu-akamai
body: (In general color for the show more expansion header feels a bit off compared to other text - maybe this component just needs a cds revamp?)

![image](https://github.com/user-attachments/assets/39d9935f-06b0-4afc-8d4c-44f1e7d79b06)

![image](https://github.com/user-attachments/assets/6878c04b-af0d-4241-a0d6-6c29da1050dc)


created_at: 2025-04-08T14:42:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,44 +45,54 @@ export const InterfaceType = ({ index }: Props) => {
     name: `linodeInterfaces.${index}.purpose`,
   });
 
-  return (
-    <FormControl>
-      <FormLabel id="network-interface" sx={{ mb: 0 }}>
-        Network Connection
-      </FormLabel>
-      <RadioGroup
-        onChange={(e, value) => {
-          // Change the interface purpose (Public, VPC, VLAN)
-          field.onChange(value);
+  const onChange = (value: InterfacePurpose) => {
+    // Change the interface purpose (Public, VPC, VLAN)
+    field.onChange(value);
+
+    const defaultFirewall = getDefaultFirewallForInterfacePurpose(
+      value,
+      firewallSettings
+    );
 
-          const defaultFirewall = getDefaultFirewallForInterfacePurpose(
-            value as InterfacePurpose,
-            firewallSettings
-          );
+    // Set the Firewall based on defaults if:
+    // - there is a default firewall for this interface type
+    // - the user has not touched the Firewall field
+    if (
+      defaultFirewall &&
+      !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
+    ) {
+      setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
+    }
+  };
 
-          // Set the Firewall based on defaults if:
-          // - there is a default firewall for this interface type
-          // - the user has not touched the Firewall field
-          if (
-            defaultFirewall &&
-            !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
-          ) {
-            setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
-          }
-        }}
-        aria-labelledby="network-interface"
-        row
-        sx={{ mb: '0px !important' }}
-        value={field.value}
+  return (
+    <FormGroup sx={{ display: 'block' }}>
+      <InputLabel id="network-connection-label">Network Connection</InputLabel>
+      <RadioGroup
+        aria-labelledby="network-connection-label"
+        sx={{ display: 'block', marginBottom: '0px !important' }}
       >
-        <FormControlLabel
-          control={<Radio />}
-          label="Public Internet"
-          value="public"
-        />
-        <FormControlLabel control={<Radio />} label="VPC" value="vpc" />
-        <FormControlLabel control={<Radio />} label="VLAN" value="vlan" />
+        <Grid2 container spacing={1}>
+          {interfaceTypes.map((interfaceType) => (
+            <SelectionCard
+              gridSize={{
+                md: 3,
+                sm: 12,
+                xs: 12,
+              }}
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
user: login: coliu-akamai
body: I can't remember - did we decide to get rid of the info helper text or are we planning to just add them back in once we have the finalized copy?

![image](https://github.com/user-attachments/assets/d434d8b3-2e09-4180-806e-93a34f75419c)

created_at: 2025-04-08T14:48:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,44 +45,54 @@ export const InterfaceType = ({ index }: Props) => {
     name: `linodeInterfaces.${index}.purpose`,
   });
 
-  return (
-    <FormControl>
-      <FormLabel id="network-interface" sx={{ mb: 0 }}>
-        Network Connection
-      </FormLabel>
-      <RadioGroup
-        onChange={(e, value) => {
-          // Change the interface purpose (Public, VPC, VLAN)
-          field.onChange(value);
+  const onChange = (value: InterfacePurpose) => {
+    // Change the interface purpose (Public, VPC, VLAN)
+    field.onChange(value);
+
+    const defaultFirewall = getDefaultFirewallForInterfacePurpose(
+      value,
+      firewallSettings
+    );
 
-          const defaultFirewall = getDefaultFirewallForInterfacePurpose(
-            value as InterfacePurpose,
-            firewallSettings
-          );
+    // Set the Firewall based on defaults if:
+    // - there is a default firewall for this interface type
+    // - the user has not touched the Firewall field
+    if (
+      defaultFirewall &&
+      !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
+    ) {
+      setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
+    }
+  };
 
-          // Set the Firewall based on defaults if:
-          // - there is a default firewall for this interface type
-          // - the user has not touched the Firewall field
-          if (
-            defaultFirewall &&
-            !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
-          ) {
-            setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
-          }
-        }}
-        aria-labelledby="network-interface"
-        row
-        sx={{ mb: '0px !important' }}
-        value={field.value}
+  return (
+    <FormGroup sx={{ display: 'block' }}>
+      <InputLabel id="network-connection-label">Network Connection</InputLabel>
+      <RadioGroup
+        aria-labelledby="network-connection-label"
+        sx={{ display: 'block', marginBottom: '0px !important' }}
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
user: login: coliu-akamai
body: Based on the UX mockups, we might not need to make the bottomMargin 0 (but may need to reduce spacing between the elements in general? Can't tell completely tbh)

![image](https://github.com/user-attachments/assets/8d1cda9f-5129-4899-8a4b-70da50d95ea0)

created_at: 2025-04-08T14:57:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,44 +45,54 @@ export const InterfaceType = ({ index }: Props) => {
     name: `linodeInterfaces.${index}.purpose`,
   });
 
-  return (
-    <FormControl>
-      <FormLabel id="network-interface" sx={{ mb: 0 }}>
-        Network Connection
-      </FormLabel>
-      <RadioGroup
-        onChange={(e, value) => {
-          // Change the interface purpose (Public, VPC, VLAN)
-          field.onChange(value);
+  const onChange = (value: InterfacePurpose) => {
+    // Change the interface purpose (Public, VPC, VLAN)
+    field.onChange(value);
+
+    const defaultFirewall = getDefaultFirewallForInterfacePurpose(
+      value,
+      firewallSettings
+    );
 
-          const defaultFirewall = getDefaultFirewallForInterfacePurpose(
-            value as InterfacePurpose,
-            firewallSettings
-          );
+    // Set the Firewall based on defaults if:
+    // - there is a default firewall for this interface type
+    // - the user has not touched the Firewall field
+    if (
+      defaultFirewall &&
+      !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
+    ) {
+      setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
+    }
+  };
 
-          // Set the Firewall based on defaults if:
-          // - there is a default firewall for this interface type
-          // - the user has not touched the Firewall field
-          if (
-            defaultFirewall &&
-            !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
-          ) {
-            setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
-          }
-        }}
-        aria-labelledby="network-interface"
-        row
-        sx={{ mb: '0px !important' }}
-        value={field.value}
+  return (
+    <FormGroup sx={{ display: 'block' }}>
+      <InputLabel id="network-connection-label">Network Connection</InputLabel>
+      <RadioGroup
+        aria-labelledby="network-connection-label"
+        sx={{ display: 'block', marginBottom: '0px !important' }}
       >
-        <FormControlLabel
-          control={<Radio />}
-          label="Public Internet"
-          value="public"
-        />
-        <FormControlLabel control={<Radio />} label="VPC" value="vpc" />
-        <FormControlLabel control={<Radio />} label="VLAN" value="vlan" />
+        <Grid2 container spacing={1}>
+          {interfaceTypes.map((interfaceType) => (
+            <SelectionCard
+              gridSize={{
+                md: 3,
+                sm: 12,
+                xs: 12,
+              }}
+              renderIcon={() => (
+                <Radio checked={field.value === interfaceType.purpose} />
+              )}
+              checked={field.value === interfaceType.purpose}
+              heading={interfaceType.label}
+              key={interfaceType.purpose}
+              onClick={() => onChange(interfaceType.purpose)}
+              subheadings={[]}
+              sxCardBaseIcon={{ svg: { fontSize: '24px !important' } }}
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
user: login: coliu-akamai
body: same here too, I think we can get rid of the `!important`
created_at: 2025-04-08T14:57:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
+        ButtonProps={{
+          TooltipProps: {
+            placement: 'right',
+          },
+          alwaysShowTooltip: disabled,
+          tooltipText: disabledReason,
+        }}
+        defaultExpanded={!disabled}
+        name="Network Interface Type"
       >
-        <FormControlLabel
-          control={<Radio />}
-          label="Configuration Profile Interfaces (Legacy)"
-          value="legacy_config"
-        />
-        <FormControlLabel
-          control={<Radio />}
-          label="Linode Interfaces (New)"
-          value="linode"
-        />
-      </RadioGroup>
-    </FormControl>
+        <FormControl disabled={disabled} sx={{ my: '0px !important' }}>
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
user: login: coliu-akamai
body: we might be able to get rid of the !important here (altho the one on line 59 seems like it's still needed)
created_at: 2025-04-08T15:06:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
user: login: coliu-akamai
body: Alignment for the radios and ShowMoreExpansion button text looks a bit off:

| this branch | ux mockups |
|-------------|------------|
|  ![image](https://github.com/user-attachments/assets/55176129-ad80-4e7c-bea0-e3aee7571275) | ![image](https://github.com/user-attachments/assets/798778b4-ce21-430e-afb1-2fe6886c9b58) |

created_at: 2025-04-08T15:09:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
user: login: coliu-akamai
body: should we also bold the radio options (if / are we planning to add copy for the interface types?)
created_at: 2025-04-08T15:25:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,44 +45,54 @@ export const InterfaceType = ({ index }: Props) => {
     name: `linodeInterfaces.${index}.purpose`,
   });
 
-  return (
-    <FormControl>
-      <FormLabel id="network-interface" sx={{ mb: 0 }}>
-        Network Connection
-      </FormLabel>
-      <RadioGroup
-        onChange={(e, value) => {
-          // Change the interface purpose (Public, VPC, VLAN)
-          field.onChange(value);
+  const onChange = (value: InterfacePurpose) => {
+    // Change the interface purpose (Public, VPC, VLAN)
+    field.onChange(value);
+
+    const defaultFirewall = getDefaultFirewallForInterfacePurpose(
+      value,
+      firewallSettings
+    );
 
-          const defaultFirewall = getDefaultFirewallForInterfacePurpose(
-            value as InterfacePurpose,
-            firewallSettings
-          );
+    // Set the Firewall based on defaults if:
+    // - there is a default firewall for this interface type
+    // - the user has not touched the Firewall field
+    if (
+      defaultFirewall &&
+      !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
+    ) {
+      setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
+    }
+  };
 
-          // Set the Firewall based on defaults if:
-          // - there is a default firewall for this interface type
-          // - the user has not touched the Firewall field
-          if (
-            defaultFirewall &&
-            !getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
-          ) {
-            setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
-          }
-        }}
-        aria-labelledby="network-interface"
-        row
-        sx={{ mb: '0px !important' }}
-        value={field.value}
+  return (
+    <FormGroup sx={{ display: 'block' }}>
+      <InputLabel id="network-connection-label">Network Connection</InputLabel>
+      <RadioGroup
+        aria-labelledby="network-connection-label"
+        sx={{ display: 'block', marginBottom: '0px !important' }}
       >
-        <FormControlLabel
-          control={<Radio />}
-          label="Public Internet"
-          value="public"
-        />
-        <FormControlLabel control={<Radio />} label="VPC" value="vpc" />
-        <FormControlLabel control={<Radio />} label="VLAN" value="vlan" />
+        <Grid2 container spacing={1}>
+          {interfaceTypes.map((interfaceType) => (
+            <SelectionCard
+              gridSize={{
+                md: 3,
+                sm: 12,
+                xs: 12,
+              }}
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx
user: login: bnussman-akamai
body: Let's add when we do copy
created_at: 2025-04-10T22:56:18Z
author_association: MEMBER
diff_hunk: @@ -37,37 +37,41 @@ export const InterfaceGeneration = () => {
     accountSettings &&
     disabledReasonMap[accountSettings.interfaces_for_new_linodes];
 
+  const disabled = disabledReason !== undefined;
+
   return (
-    <FormControl disabled={Boolean(disabledReason)}>
-      <FormLabel
-        id="interface-generation"
-        sx={{ alignItems: 'center', display: 'flex', mb: 0 }}
-      >
-        Interface Generation
-        {disabledReason && (
-          <TooltipIcon
-            status="help"
-            sxTooltipIcon={{ p: 0.5 }}
-            text={disabledReason}
-          />
-        )}
-      </FormLabel>
-      <RadioGroup
-        aria-labelledby="interface-generation"
-        onChange={field.onChange}
-        value={field.value ?? 'legacy_config'}
+    <Box>
+      <ShowMoreExpansion
+        ButtonProps={{
+          TooltipProps: {
+            placement: 'right',
+          },
+          alwaysShowTooltip: disabled,
+          tooltipText: disabledReason,
+        }}
+        defaultExpanded={!disabled}
+        name="Network Interface Type"
path: packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx
user: login: bnussman-akamai
body: Will revisit 
created_at: 2025-04-10T22:56:45Z
author_association: MEMBER

PR comments:
user: login: coliu-akamai
created_at: 2025-04-07T22:56:04Z
body: awesome thanks Banks! Initial look through, will go through more tomorrow

commented on an earlier version, ignore the first two bullets
- ~~not seeing the updated radio selects, but I'm p sure I'm on your latest commit?~~
- ~~heads up that unit test failures are related~~ 
- didn't catch this last time, but for the new UI using legacy VPC interface, general range errors don't get surfaced. (Saw this when working with IP ranges, started something [here](https://github.com/linode/manager/compare/develop...coliu-akamai:manager:ip-ranges-general-error?expand=1) to fix - we could just add it to this pr?) 
![image](https://github.com/user-attachments/assets/84049d1d-57dc-4a2c-8cca-2ab6c55a6b8b) 
![image](https://github.com/user-attachments/assets/5bb82946-19fb-405b-bd0d-32af030f0b83)


author_association: CONTRIBUTOR
user: login: linode-gh-bot
created_at: 2025-04-10T23:40:30Z
body: <!-- PULL REQUEST COMMENT: cloud-manager-ci-comment -->
<!-- BEGIN COMMENT SECTION: cloud-manager-ui-tests -->
## Cloud Manager UI test results
### :small_red_triangle: 1 failing test on [test run #5 ↗︎](https://baker.linode.com/job/manager/job/manager-test/job/PR-11985/5/display/redirect)

<table><thead><tr><td><strong>:x: Failing</strong></td><td><strong>:white_check_mark: Passing</strong></td><td><strong>:arrow_right_hook: Skipped</strong></td><td><strong>:clock1: Duration</strong></td></tr></thead><tbody><tr><td><code>1 Failing</code></td><td><code>539 Passing</code></td><td><code>4 Skipped</code></td><td><code>116m 56s</code></td></tr></tbody></table>


#### Details
<table><thead><tr><th colspan="3">Failing Tests</th></tr><tr><th></th><th>Spec</th><th>Test</th></tr></thead><tbody>
<tr><td>:x:</td><td><code>rebuild-linode.spec.ts</code></td><td><em>Cloud Manager Cypress Tests→rebuild linode » rebuild linode</em></td></tr>
</tbody></table>

#### Troubleshooting
Use this command to re-run the failing tests:

```bash
pnpm cy:run -s "cypress/e2e/core/linodes/rebuild-linode.spec.ts"
```
<!-- END COMMENT SECTION: cloud-manager-ui-tests -->
author_association: NONE