Migrating from Elastic EUI to MUI, Chakra UI, or Ant Design — Complete Guide
Last updated: April 2026 · 12 min read
Why Teams Migrate Away from Elastic EUI
Elastic EUI (Elastic User Interface) is a well-crafted React component library originally built for Kibana, Elasticsearch dashboards, and other Elastic products. While it provides a polished set of data-dense components, many teams discover significant drawbacks when using EUI outside the Elastic ecosystem. Understanding these limitations helps you decide whether migration is worth the investment.
Tight coupling to the Elastic ecosystem.EUI was designed with Elastic products in mind. Its design language, component behavior, and even its color palette reflect Elastic’s brand identity. Teams building non-Elastic applications often find themselves fighting the library’s opinionated defaults rather than embracing them. Theming capabilities exist but are limited compared to general-purpose libraries like MUI or Chakra UI, where brand customization is a first-class concern.
Bundle size concerns.EUI ships approximately 188KB gzipped in a typical installation, which is notably larger than alternatives. MUI’s tree-shakeable architecture lets you import only the components you use, often resulting in 40-60% smaller production bundles. For teams targeting mobile-first audiences or optimizing Core Web Vitals, this difference in initial load time can directly impact user experience and search engine rankings.
Licensing and governance. EUI is released under the Elastic License 2.0 (ELv2), which is not an OSI-approved open source license. While it permits most use cases, the license includes restrictions on providing the software as a managed service. Legal teams at some organizations flag this as a compliance risk, especially for SaaS products. MUI (MIT), Chakra UI (MIT), and Ant Design (MIT) all use permissive licenses with no such restrictions.
Limited community outside Elastic. The EUI community is primarily composed of Elastic employees and contributors to the Elastic Stack. Stack Overflow questions, third-party tutorials, blog posts, and community plugins are significantly fewer compared to MUI (which has over 90,000 GitHub stars) or Ant Design (which dominates the enterprise UI space in Asia and increasingly worldwide). When your team encounters an edge case, finding community solutions for EUI is often harder than for mainstream alternatives.
Component Mapping: EUI to MUI
Below is a detailed mapping of the most commonly used Elastic EUI components and their Material UI equivalents. Each example shows the EUI source code on the left and the corresponding MUI code on the right. You can also use the FrontFamily Converter to automate these transformations instantly.
EuiButton → Button
<EuiButton fill color="primary"> Save Changes </EuiButton> <EuiButton color="danger" isDisabled> Delete </EuiButton>
<Button variant="contained" color="primary"> Save Changes </Button> <Button color="error" disabled> Delete </Button>
EuiFieldText → TextField
<EuiFieldText
placeholder="Enter your name"
value={name}
onChange={(e) => setName(e.target.value)}
fullWidth
isInvalid={!isValid}
/><TextField
placeholder="Enter your name"
value={name}
onChange={(e) => setName(e.target.value)}
fullWidth
error={!isValid}
variant="outlined"
/>EuiCard → Card
<EuiCard
title="Analytics"
description="View your data"
icon={<EuiIcon type="dashboard" />}
/><Card>
<CardContent>
<Typography variant="h6">Analytics</Typography>
<Typography variant="body2">View your data</Typography>
</CardContent>
</Card>EuiText → Typography
<EuiText size="m"> <h2>Section Title</h2> <p>Body content here.</p> </EuiText> <EuiTextColor color="subdued"> Secondary text </EuiTextColor>
<Typography variant="h5">Section Title</Typography> <Typography variant="body1">Body content here.</Typography> <Typography color="text.secondary"> Secondary text </Typography>
EuiModal → Dialog
<EuiModal onClose={closeModal}>
<EuiModalHeader>
<EuiModalHeaderTitle>Confirm</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>Are you sure?</EuiModalBody>
<EuiModalFooter>
<EuiButton onClick={closeModal}>Cancel</EuiButton>
</EuiModalFooter>
</EuiModal><Dialog open={isOpen} onClose={closeModal}>
<DialogTitle>Confirm</DialogTitle>
<DialogContent>Are you sure?</DialogContent>
<DialogActions>
<Button onClick={closeModal}>Cancel</Button>
</DialogActions>
</Dialog>EuiCallOut → Alert
<EuiCallOut title="Update available" color="warning" iconType="alert" > A new version is ready. </EuiCallOut>
<Alert severity="warning"> <AlertTitle>Update available</AlertTitle> A new version is ready. </Alert>
EuiBadge → Chip
<EuiBadge color="success">Active</EuiBadge> <EuiBadge color="hollow">Draft</EuiBadge>
<Chip label="Active" color="success" /> <Chip label="Draft" variant="outlined" />
Prop Conversion Reference
Beyond component name changes, migrating from EUI to MUI requires mapping EUI-specific props to their MUI equivalents. Here are the most common prop transformations you will encounter during a migration:
| EUI Prop | MUI Equivalent | Notes |
|---|---|---|
fill | variant="contained" | EUI boolean becomes MUI variant |
isDisabled | disabled | Boolean prop rename |
color="danger" | color="error" | Semantic color mapping |
color="ghost" | variant="text" | Transparent background button |
isInvalid | error | Form validation state |
isLoading | loading | Button loading state |
size="s" | size="small" | EUI abbreviates, MUI spells out |
iconType="search" | startIcon={<SearchIcon />} | String icon ref to component |
fullWidth | fullWidth | Same prop, same behavior |
Which Framework Should You Migrate To?
The best migration target depends on your team’s priorities, application type, and design requirements. Here is how the three most popular alternatives compare for teams leaving EUI:
Material UI (MUI) — Best for Enterprise Applications
MUI is the most mature React component library with over 90,000 GitHub stars and a massive ecosystem. It offers a comprehensive set of components including DataGrid, DatePicker, and TreeView that are essential for data-heavy enterprise applications. If your EUI project involves dashboards, admin panels, or complex data tables, MUI provides the closest feature parity. The theming system is deeply customizable, and the MUI X suite offers advanced commercial components that match or exceed EUI’s data visualization capabilities.
Chakra UI — Best for Developer Experience
Chakra UI prioritizes developer ergonomics with its style props system, where layout and styling are expressed directly as component props. If your team values rapid prototyping, composable primitives, and a smaller learning curve, Chakra is an excellent choice. Its accessibility-first approach means every component ships with correct ARIA attributes, focus management, and keyboard navigation. However, Chakra lacks advanced data components like DataGrid, so teams with heavy tabular data requirements may need to pair it with a dedicated table library.
Ant Design — Best for Data-Dense Interfaces
Ant Design excels in enterprise scenarios with high information density. Its Table component, form system, and comprehensive set of data entry components make it a natural fit for teams migrating from EUI’s data-centric design philosophy. Ant Design also provides built-in internationalization for 50+ locales, a robust icon library, and a design system that scales well for large teams. The trade-off is a larger bundle size and a more opinionated visual style that can require significant effort to customize away from the default look.
Full Component Mapping Table
Below is a comprehensive mapping of EUI components to their equivalents across MUI, Chakra UI, and Ant Design. Use this table as a quick reference when planning your migration:
| Elastic EUI | MUI | Chakra UI | Ant Design |
|---|---|---|---|
| EuiButton | Button | Button | Button |
| EuiFieldText | TextField | Input | Input |
| EuiCard | Card | Box | Card |
| EuiText | Typography | Text | Typography |
| EuiAvatar | Avatar | Avatar | Avatar |
| EuiBadge | Chip | Badge / Tag | Tag |
| EuiModal | Dialog | Modal | Modal |
| EuiSwitch | Switch | Switch | Switch |
| EuiCallOut | Alert | Alert | Alert |
| EuiSelect | Select | Select | Select |
| EuiLoadingSpinner | CircularProgress | Spinner | Spin |
| EuiProgress | LinearProgress | Progress | Progress |
| EuiToolTip | Tooltip | Tooltip | Tooltip |
| EuiBasicTable | DataGrid | Table (manual) | Table |
Step-by-Step Migration Strategy
Migrating from Elastic EUI to another component library requires careful planning. Here is a proven strategy that minimizes risk and allows your team to deliver incrementally:
Step 1: Audit your EUI usage
Before writing any migration code, run a full inventory of every EUI component used in your codebase. Use a tool like grep -r "from '@elastic/eui'" to generate a complete import list. Group components by complexity: simple (Button, Badge, Avatar), medium (TextField, Select, Switch), and complex (BasicTable, Modal, DatePicker). This inventory becomes your migration backlog and helps you estimate the total effort accurately.
Step 2: Install the target library alongside EUI
Both libraries can coexist in the same project. Install your target library (MUI, Chakra, or Ant Design) and its peer dependencies without removing EUI. If migrating to MUI, install @mui/material, @emotion/react, and @emotion/styled. Verify that the CSS reset and global styles from both libraries do not conflict by rendering a test page with components from each.
Step 3: Create an abstraction layer
Build a thin wrapper component library that exports your own Button, Input, Card, etc. Initially these wrappers render EUI components internally. As you migrate, swap the internals to the new library without changing the public API. This pattern isolates consumers from the migration and lets you switch component-by-component without touching every file that uses a Button.
Step 4: Migrate in waves by complexity
Start with simple leaf components (Button, Badge, Avatar, Typography) in week one. Move to form components (TextField, Select, Checkbox, Switch) in week two. Tackle layout components (Card, Modal, Tabs, Accordion) in week three. Save data-heavy components (BasicTable, DataGrid, DatePicker) for last, as these require the most testing and often need custom configuration.
Step 5: Remove EUI and clean up
Once all components are migrated, remove @elastic/eui and its dependencies from your package.json. Run a final search for any remaining EUI imports. Audit your bundle size to confirm the expected reduction. Update your CI pipeline to flag any future EUI imports as lint errors to prevent regression.
What Developers Actually Hit
Based on migration reports from engineering teams. These are the problems documentation doesn’t warn you about.
EUI wraps theme, i18n, toast notifications, and global component defaults in a single EuiProvider. Extracting components one-by-one means either keeping EuiProvider alive for the remaining components (bloating the bundle with the entire EUI runtime) or breaking them all at once in a big-bang migration. There is no clean way to partially unwrap what EuiProvider provides.
EUI manages pagination internally within EuiBasicTable. MUI’s DataGrid requires explicit paginationModel state. Teams that relied on EUI’s “just pass items and it works” pattern discover they need to manage page index, page size, and total count themselves. What was zero state management becomes a useState + onPaginationModelChange handler on every table.
EUI uses semantic color names (subdued, accent, success) that map to its own palette. MUI uses Material Design colors (primary, secondary, error). There is no 1:1 mapping — subdued has no MUI equivalent, and teams end up with a custom theme layer just to bridge the gap between the two color systems.
EUI depends on @elastic/datemath and moment. These are heavy dependencies that MUI doesn’t need. But if your app uses EuiSuperDatePicker’s relative date expressions ("now-15m", "now/d"), you need to keep these dependencies or completely rewrite the date parsing logic. There is no MUI component that understands Elastic’s relative date syntax.
Convert EUI to MUI instantly
Skip the manual work. Paste your Elastic EUI code into the FrontFamily Converter and get production-ready MUI output with correct imports, prop mappings, and component restructuring. A pre-loaded EuiCard example is ready for you to try.
Open Converter with EUI → MUI ExampleCommon Pitfalls When Migrating from EUI
EUI’s global CSS reset.EUI injects a comprehensive CSS reset and global styles that affect typography, spacing, and element defaults across your entire application. When you remove EUI, elements that relied on these global styles may shift unexpectedly. Before removing EUI, audit which global styles your application depends on and replicate them in your own stylesheet or in the target library’s theme configuration.
Icon system differences. EUI uses a string-based icon system where icons are referenced by name (iconType="search"). MUI, Chakra, and Ant Design all use component-based icons. You will need to create a mapping from EUI icon names to the equivalent icon components in your target library. Consider using lucide-react or react-icons as a universal icon solution that works across all frameworks.
EuiProvider and service dependencies. EUI provides services through its EuiProvider context including internationalization, color mode, and component defaults. Components that rely on these services will break when extracted from the provider tree. Identify all provider-dependent features early and plan equivalent solutions in your target library.
TypeScript Migration Patterns
Migrating components is only half the battle. EUI's type system is heavily reliant on complex generics and custom utility types. These are the patterns that consume most of a senior engineer's migration time.
A. Generic Type Mapping (Data Tables)
EUI uses generics to enforce type safety on row items. When moving to MUI's DataGrid, you must map these generic definitions — the column type names and prop shapes differ significantly.
import type { EuiBasicTableColumn } from '@elastic/eui';
interface User {
id: string;
name: string;
role: string;
status: 'active' | 'inactive';
}
const columns: Array<EuiBasicTableColumn<User>> = [
{ field: 'name', name: 'Full Name', sortable: true },
{ field: 'role', name: 'Role' },
{
field: 'status',
name: 'Status',
render: (status: User['status']) => (
<EuiHealth color={status === 'active' ? 'success' : 'danger'}>
{status}
</EuiHealth>
),
},
];import type { GridColDef } from '@mui/x-data-grid';
interface User {
id: string;
name: string;
role: string;
status: 'active' | 'inactive';
}
const columns: GridColDef<User>[] = [
{ field: 'name', headerName: 'Full Name', sortable: true },
{ field: 'role', headerName: 'Role' },
{
field: 'status',
headerName: 'Status',
renderCell: ({ row }) => (
<Chip
color={row.status === 'active' ? 'success' : 'error'}
label={row.status}
size="small"
/>
),
},
];- 1.
EuiBasicTableColumn<T>→GridColDef<T>— different generic wrapper - 2.
name→headerName— column display name - 3.
render: (value: T[K]) =>→renderCell: ({ row }) =>— render function receives params object, not raw value - 4. MUI DataGrid requires
@mui/x-data-gridas a separate package (not in core)
B. Dealing with ExclusiveUnion
Standard TypeScript unions (A | B) allow properties from both types to coexist. EUI uses a custom ExclusiveUnion utility to strictly forbid mixing props — for example, preventing an EuiButton from accepting both onClick (button behavior) and href (anchor behavior) simultaneously.
MUI handles this differently using the component prop and OverridableComponent utility type.
// ✅ Valid — button behavior only
<EuiButton onClick={handleClick}>Save</EuiButton>
// ✅ Valid — anchor behavior only
<EuiButton href="/login">Login</EuiButton>
// ❌ TypeScript Error!
// ExclusiveUnion prevents both onClick AND href
<EuiButton href="/login" onClick={handleClick}>
Login
</EuiButton>// ✅ Default — renders as <button>
<Button onClick={handleClick}>Save</Button>
// ✅ As anchor — renders as <a>
<Button component="a" href="/login">Login</Button>
// ✅ As Next.js Link
<Button component={Link} href="/login">
Login
</Button>
// TypeScript knows which props are valid
// based on the component prop valueIf you have wrapper components that forward EUI's ExclusiveUnion types, rewrite them using MUI's typed component pattern:
// Before: EUI wrapper with ExclusiveUnion
type Props = EuiButtonProps; // includes ExclusiveUnion internally
// After: MUI wrapper with generic component type
import type { ButtonProps } from '@mui/material/Button';
// For button usage:
type Props = ButtonProps<'button'>;
// For anchor usage:
type Props = ButtonProps<'a', { href: string }>;
// For polymorphic usage:
type Props<C extends React.ElementType = 'button'> = ButtonProps<C>;C. EUI-Specific Type Utilities You'll Lose
| EUI Type | Purpose | MUI Equivalent |
|---|---|---|
| ExclusiveUnion<A, B> | Strict either/or props | Use OverridableComponent or discriminated unions |
| EuiBasicTableColumn<T> | Typed table columns | GridColDef<T> |
| EuiComboBoxOptionOption<T> | Typed combobox options | Use AutocompleteProps<T, ...> — 4 generic params |
| PropertySort | Sort configuration | GridSortModel |
| Pagination | Pagination state | GridPaginationModel |
| EuiSelectableOption<T> | Typed selectable items | No direct equivalent — use custom interface with MUI Select |
Interactive Component Reference
Search any EUI component to find its MUI equivalent. Components marked with ⚠ have no direct replacement and require custom implementation.
| Elastic EUI | Material UI | Props / Notes | |
|---|---|---|---|
EuiButtonfill, color, isDisabled | Buttonvariant="contained", color, disabled | fill→variant="contained", no fill→variant="outlined" | |
EuiButtonEmptycolor, size | Buttonvariant="text" | EUI has a separate component; MUI uses variant prop | |
EuiButtonIconiconType, color | IconButtoncolor | iconType→pass icon as children | |
EuiFieldTextplaceholder, value, isInvalid | TextFieldplaceholder, value, error | isInvalid→error (boolean) | |
EuiFieldPasswordtype, value | TextFieldtype="password", value | EUI has dedicated component; MUI uses type prop | |
EuiFieldNumbervalue, min, max | TextFieldtype="number", value, inputProps={{ min, max }} | Number constraints go in inputProps | |
EuiTextAreavalue, rows | TextFieldmultiline, value, rows | Add multiline prop | |
EuiFieldSearchvalue, onChange | TextFieldvalue, onChange, InputProps={{ startAdornment }} | Add search icon via InputAdornment | |
EuiCardtitle, description, hasShadow | Card + CardContentelevation, + Typography inside | MUI Card needs sub-components for structure | |
EuiPanelpaddingSize, hasShadow | Paperelevation, sx={{ p }} | EuiPanel is a generic container; Paper is closest | |
EuiTextsize, color="subdued" | Typographyvariant, color="textSecondary" | size "xs"→"caption", "s"→"body2", "m"→"body1" | |
EuiTitlesize="l" | Typographyvariant="h4" | size "l"→"h4", "m"→"h5", "s"→"h6" | |
EuiAvatarname, imageUrl, size | Avataralt, src, sx={{ width, height }} | imageUrl→src, size needs sx override | |
EuiBadgecolor, iconType | Chipcolor, icon | EuiBadge is simpler; Chip has more variants | |
EuiModalonClose | DialogonClose, open | Needs separate open state; EUI handles internally | |
EuiModalHeader | DialogTitle | Sub-component mapping | |
EuiModalBody | DialogContent | Sub-component mapping | |
EuiModalFooter | DialogActions | Sub-component mapping | |
EuiConfirmModaltitle, onConfirm, onCancel | Dialog + customopen, onClose + buttons | No direct equivalent; build with Dialog + DialogActions | |
EuiSwitchchecked, onChange, label | Switch + FormControlLabelchecked, onChange, label | MUI Switch needs FormControlLabel wrapper for label | |
EuiCheckboxchecked, onChange, label, id | Checkbox + FormControlLabelchecked, onChange, label | Same wrapper pattern as Switch | |
EuiCallOuttitle, color, iconType | Alertseverity, icon | color "danger"→severity="error", "success"→"success" | |
EuiSelectoptions, value, onChange | Select + MenuItemvalue, onChange + children | MUI Select uses MenuItem children instead of options array | |
EuiSuperSelectoptions, valueOfSelected | Select + MenuItemvalue, renderValue | SuperSelect rich options need custom MenuItem rendering | |
EuiComboBoxoptions, selectedOptions, onChange | Autocompleteoptions, value, onChange, renderInput | Complex mapping — needs renderInput prop | |
EuiLoadingSpinnersize | CircularProgresssize | size "l"→40, "m"→24, "s"→16 | |
EuiProgressvalue, max, size | LinearProgressvalue, variant="determinate" | value is 0-100 in MUI (percentage), not raw value | |
EuiToolTipcontent, position | Tooltiptitle, placement | content→title, position→placement | |
EuiHorizontalRule | Divider | Direct replacement | |
EuiTabschildren EuiTab | Tabs + Tabvalue, onChange | MUI Tabs are controlled; EUI allows uncontrolled | |
EuiBasicTableitems, columns, pagination | DataGridrows, columns, paginationModel | Significant API difference — MUI DataGrid is @mui/x-data-grid | |
EuiInMemoryTableitems, columns, search | DataGrid + custom filter | Built-in search needs custom toolbar in MUI | |
EuiFlyoutonClose, side | DraweronClose, anchor | side→anchor ("right"→"right") | |
EuiPopoverbutton, isOpen, closePopover | PopoveranchorEl, open, onClose | MUI uses anchorEl ref pattern instead of button prop | |
EuiContextMenupanels | Menu + MenuItemanchorEl, open | EUI panel-based navigation has no direct MUI equivalent | |
EuiSpacersize | Boxsx={{ my }} | Use Box with margin or Stack spacing | |
EuiFlexGroupgutterSize, direction | Stack or Boxspacing, direction | MUI Stack is the closest equivalent | |
EuiFlexItemgrow | Boxsx={{ flexGrow }} | No dedicated component in MUI | |
EuiAccordionbuttonContent, isLoading | Accordion + AccordionSummaryexpanded, expandIcon | MUI needs sub-components | |
EuiDatePickerselected, onChange | DatePicker (@mui/x-date-pickers)value, onChange | Requires @mui/x-date-pickers package | |
EuiSuperDatePickerstart, end, onTimeChange | No direct equivalent | No MUI equivalent — build custom with two DatePickers + presets | |
EuiGlobalToastListtoasts, dismissToast | No direct equivalent | No direct equivalent — use notistack or build custom Snackbar manager | |
EuiHealthcolor | No direct equivalent | Simple colored dot — build with Box sx={{ width: 8, height: 8, borderRadius: "50%" }} | |
EuiStattitle, description | No direct equivalent | No MUI equivalent — build custom with Typography + Box | |
EuiColorPickercolor, onChange | No direct equivalent | No MUI equivalent — use react-colorful or similar | |
EuiMarkdownEditorvalue, onChange | No direct equivalent | No MUI equivalent — use @uiw/react-md-editor or similar | |
EuiCodeBlocklanguage, children | No direct equivalent | No MUI equivalent — use react-syntax-highlighter or prism-react-renderer | |
EuiProvidercolorMode | ThemeProvider + CssBaselinetheme | EUI provider wraps theme + globals; MUI separates them |
Import Changes at a Glance
import {
EuiButton,
EuiFieldText,
EuiCard,
EuiText,
EuiModal,
EuiCallOut,
EuiBadge,
} from '@elastic/eui';import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import { Card, CardContent } from '@mui/material';
import Typography from '@mui/material/Typography';
import Dialog from '@mui/material/Dialog';
import Alert from '@mui/material/Alert';
import Chip from '@mui/material/Chip';