Patterns

Detail Screen

The create/edit screen recipe: breadcrumb → vertical Form in a Card → sticky AppFormFooter with a btn-ant-primary Save and an opacity-8 Cancel.

Source in the MFEs
SchemaConfigurationCreate.tsxTemplateConfigurationCreate.tsxCreateGroup.vueSettingEdit.vueracar-fe AppFormFooter.tsx

Anatomy

Every create/edit screen is the same three bands, top to bottom:

  1. 1BreadcrumbAppBreadcrumb — where the user is; showBack (Vue) or router history handles navigation back.
  2. 2Form in Card(s)Form layout="vertical" wrapping one or more Cards — labels stack above inputs, fields in a Row/Col grid, every Col carries xs={24}.
  3. 3Sticky footerAppFormFooter as the LAST child inside the Card body — a btn-ant-primary Save (htmlType="submit") and an opacity-8 Cancel, pinned to the bottom while scrolling.

The recipe only assembles pieces documented elsewhere in this kit: the sticky bar is the Form Footer pattern, and the field conventions live on the Input component page.

Live example

A working mini create screen. Submit runs validation and shows the Save button’s loading state; Cancel resets the fields.

Preview
Create Currency
/* REACT (racar-fe, rate-fe, reference-fe): Form + Card + Sticky Footer */
import AppFormFooter from '@/components/common/AppFormFooter';
import { Button, Card, Form, Input, Row, Col } from 'antd';
import React from 'react';

const CreatePage: React.FC = () => {
  const [form] = Form.useForm();
  const [loading, setLoading] = React.useState(false);

  const handleSubmit = async (values: any) => {
    setLoading(true);
    try {
      // API call
      console.log('Submit:', values);
    } finally {
      setLoading(false);
    }
  };

  const handleCancel = () => navigate(-1);

  return (
    <Row className="w-100">
      <AppBreadcrumb items={breadcrumbItems} translate={t} />
      <Col span={24}>
        <Form
          form={form}
          layout="vertical"
          onFinish={handleSubmit}
        >
          <Card style={{ borderRadius: 20 }} styles={{ body: { padding: 18 } }}>
            <Row gutter={[16, 12]}>
              <Col xs={24} md={12}>
                <Form.Item
                  name="fieldName"
                  label="Field Label"
                  rules={[{ required: true }]}
                >
                  <Input placeholder="Enter value" size="large" />
                </Form.Item>
              </Col>
            </Row>

            <AppFormFooter>
              <Button
                type="primary"
                loading={loading}
                htmlType="submit"
              >
                Save
              </Button>
              <Button onClick={handleCancel}>Cancel</Button>
            </AppFormFooter>
          </Card>
        </Form>
      </Col>
    </Row>
  );
};

Wiring

The parts that make the recipe work, in order:

  • Form layout="vertical" + onFinish (React) / @finish (Vue) — validation from Form.Item rules fires on submit via htmlType="submit" / html-type="submit".
  • The Form wraps the Card (React) or lives inside it (Vue) — either way AppFormFooter is the last child inside the Card body and inside the Form, so the submit button stays in Form context and the sticky bar bleeds to the card edges.
  • loading on the Save button disables it while the API call is in flight; Cancel navigates back (navigate(-1) / router.go(-1)) or resets the form.
  • House button classes: btn-ant-primary on Save, opacity-8 on Cancel — same pair on every module’s footer.

Rules

Footer margins must match the card padding
AppFormFooter’s negative margins have to equal the module’s actual .ant-card-body padding or the bar won’t bleed to the card edges — verify the App.css padding override before copying CSS between modules. Details on the Form Footer page.
Always xs={24}
Every Col must carry xs={24} (don’t omit it) or the responsive layout breaks on mobile — fields should stack full-width below the md breakpoint.
Breadcrumb broadcasts the page title
AppBreadcrumb dispatches CustomEvent('rvn-active-page-title') so the shell header can show the page name on mobile — required for embedded MFEs, so don’t replace it with a bare AntD Breadcrumb in production pages.
Match button sizes to the page
Button sizes vary by module — size="large" in racar-fe (40px, matches its large inputs), default middle (32px) in administrator-fe. Check the page’s existing buttons before picking one.